এই পর্বে যা শিখবেন
- Array তৈরির চার উপায়, আর
new Array(3)-এর ফাঁকা ঘরের ফাঁদ length, index,at(-1)আর sparse array — পড়া, লেখা, কাটাpush/pop/shift/unshift/splice/slice— কোনটি কী ফেরত দেয়, আর কোনটি array বদলায়indexOf,includes,find,some,everyদিয়ে খোঁজা — আরNaN-এর রহস্য- Reference, কপি, 2D array, array-like আর typed array — আর AI-এর লেখা array-কোড যাচাইয়ের চেকলিস্ট
১ · array কী — ক্রমে সাজানো মানের তালিকা, আর ভেতরে একটি object
আগের পর্বে object দেখেছেন — নাম দিয়ে মান রাখা: student.name, student.roll. কিন্তু অনেক সময় নাম দরকার নেই, দরকার ক্রম: বাজারের তালিকা, ক্লাসের সবার নম্বর, ডাক্তারের চেম্বারে অপেক্ষার সারি। “প্রথমটি”, “তৃতীয়টি”, “শেষটি” — এভাবেই আমরা এদের কথা বলি।
এর জন্য array. Array হলো ক্রমে সাজানো মানের একটি তালিকা, যার প্রতিটি মানের একটি সংখ্যা-ঠিকানা থাকে — index. প্রথম মানের index 0, দ্বিতীয়টির 1, আর শেষটির length - 1.
মেট্রো রেলের একটি ট্রেন ভাবুন। বগিগুলো পাশাপাশি সাজানো, প্রতিটির গায়ে নম্বর। “৩ নম্বর বগিতে উঠুন” বললেই জায়গা নিশ্চিত — নাম জানার দরকার নেই। JavaScript-এর array-র বিশেষত্ব হলো, এই ট্রেনে চলার পথেই বগি জোড়া বা খোলা যায়, আর একেক বগিতে একেক রকম মাল রাখা যায় — যাত্রী, পার্সেল, এমনকি আরেকটি ছোট ট্রেন!
JavaScript-এর array তিনটি দিক থেকে C বা Java-র array থেকে আলাদা:
বাড়ে-কমে
আকার আগে বলতে হয় না। push করলে বড় হয়, pop করলে ছোট — engine নিজে মেমরি সামলায়।
মিশ্র টাইপ
[1, 'দুই', true, { x: 3 }] বৈধ। তবে বাস্তবে এক array-তে এক ধরনের মান রাখাই ভালো অভ্যাস।
আসলে object
typeof [] হলো 'object'. Index-গুলো ভেতরে "0", "1" নামের property, আর length একটি বিশেষ property.
reference দিয়ে চলে
Variable-এ array নিজে থাকে না, থাকে তার ঠিকানা। দুই variable একই array দেখাতে পারে — অংশ ৭।
পর্ব ১৪-এর মতোইএই পর্বের কেন্দ্রীয় প্রশ্ন একটাই: এই কাজটি কি array-টিকেই বদলাবে, নাকি একটি নতুন array দেবে? sort() আপনার array-টিকেই সাজিয়ে ফেলে; toSorted() একটি সাজানো কপি দেয়, আসলটি অক্ষত রাখে। দুটোর নাম কাছাকাছি, ফল পুরো আলাদা। একই array যদি প্রোগ্রামের দুই জায়গা থেকে দেখা হয়, এক জায়গার mutation অন্য জায়গাকে চুপচাপ বদলে দেয় — error ছাড়াই। প্রতিটি method শেখার সময় তাই নিজেকে জিজ্ঞেস করুন: “বদলায়, নাকি নতুন দেয়? আর ফেরত দেয় কী?”
C আর Java-র array নির্দিষ্ট আকারের, এক টাইপের পাশাপাশি ঘর; Java-তে বাড়ানো-কমানোর জন্য আলাদা ArrayList লাগে। JavaScript-এর array আচরণে Python-এর list-এর সবচেয়ে কাছে — বাড়ে-কমে, মিশ্র টাইপ রাখে। পার্থক্য: a[-1] শেষ উপাদান দেয় না (লাগে a.at(-1)), slicing syntax নেই (a.slice(1, 3) method), আর সীমার বাইরে পড়লে Python-এর মতো IndexError বা C-র মতো undefined behaviour নয় — চুপচাপ undefined. তুলনার জন্য দেখুন Python-এর list, Java-র List আর C-র array.
২ · array তৈরি — literal, Array.of, Array.from আর new Array-এর ফাঁদ
প্রতিদিনের কাজে array তৈরির উপায় একটাই — array literal, অর্থাৎ বর্গবন্ধনীর ভেতরে কমা দিয়ে মান: [], [320, 145, 199]. শেষ মানের পরে একটি বাড়তি কমা (trailing comma) রাখাও বৈধ — লম্বা তালিকায় নতুন লাইন যোগ করা সহজ হয়। কিন্তু আরও তিনটি উপায় জানা দরকার, কারণ এদের একটি বিখ্যাত ফাঁদ:
| লিখলেন | পেলেন (Node-এ চালিয়ে দেখা) | মনে রাখুন |
|---|---|---|
[3] | [ 3 ] | একটি মান, 3 — সবচেয়ে সরল |
Array.of(3) | [ 3 ] | যা দেবেন, তা-ই উপাদান (ES2015) |
new Array(3) | [ <3 empty items> ] | ৩টি ফাঁকা ঘর, মান নয়! |
new Array(3, 4) | [ 3, 4 ] | দুই বা বেশি argument দিলে আবার উপাদান |
Array.from({ length: 3 }, (_, i) => i * 2) | [ 0, 2, 4 ] | দৈর্ঘ্য + প্রতিটি ঘরের মান বানানোর function |
Array.from('ঢাকা') | [ 'ঢ', 'া', 'ক', 'া' ] | string → code point-এর array |
Array.from(new Set([1, 2, 2, 3])) | [ 1, 2, 3 ] | যেকোনো iterable থেকে — পুনরাবৃত্তি বাদ দেওয়ার চেনা কৌশল |
Array constructor-এর আচরণ argument-এর সংখ্যার ওপর নির্ভর করে: একটি number দিলে সেটি “দৈর্ঘ্য”, বেশি দিলে “উপাদান”। এই দ্বৈত আচরণ গোলমেলে বলেই ES2015-এ Array.of এসেছে — যা দেবেন, সবসময় উপাদান। আর Array.from দুটি কাজ একসাথে করে: যেকোনো iterable বা array-র মতো জিনিস থেকে আসল array বানায়, আর দ্বিতীয় argument হিসেবে একটি function দিলে প্রতিটি উপাদান বানানোর সময়ই রূপান্তর করে নেয়।
ফাঁকা ঘর (hole) — new Array(3).map(...) কেন কিছুই করে না
ধরুন ১ থেকে ৫ পর্যন্ত রোল নম্বরের array চান। অনেকে লেখেন:
const rolls = new Array(5).map((_, i) => i + 1);
console.log(rolls); // [ <5 empty items> ] — Node-এর আউটপুট
প্রত্যাশা ছিল [ 1, 2, 3, 4, 5 ]. কেন হলো না? new Array(5) শুধু length 5 বসায় — "0" থেকে "4" নামের কোনো property তৈরি করে না। এমন না-থাকা ঘরকে বলে hole, আর যে array-তে hole আছে তাকে sparse array. map, forEach, filter-এর মতো পুরোনো method hole-গুলো টপকে যায় — callback একবারও ডাকে না। সমাধান দুটি:
Array.from({ length: 5 }, (_, i) => i + 1)— সবচেয়ে পরিষ্কার; প্রতিটি ঘর আসল মান পায়।new Array(5).fill(0)— আগে প্রতিটি ঘর ভরে দিন, তারপরmap.
fill-এ object দিলে সব ঘরে একই object. Array(3).fill([]) তিনটি আলাদা খালি array বানায় না — একটি array-র reference তিন ঘরে বসায়। তাই প্রথমটিতে push(1) করলে ফল [ [ 1 ], [ 1 ], [ 1 ] ] (Node-এ চালিয়ে দেখা)। আলাদা আলাদা ভেতরের array চাইলে Array.from({ length: 3 }, () => []) — function প্রতি ঘরের জন্য আলাদা করে ডাকা হয়। 2D seat plan বানাতে গিয়ে এই ভুল খুব সাধারণ।
৩ · index, length আর at() — পড়া, লেখা আর কেটে ছোট করা
Index দিয়ে পড়া আর লেখা দুটোই বর্গবন্ধনীতে: cart[0] পড়ে প্রথম পণ্য, cart[1] = 'মসুর ডাল' দ্বিতীয়টি বদলে দেয়। সীমার বাইরে পড়লে — cart[10] — কোনো error নয়, undefined. এই “চুপচাপ undefined” সুবিধাও, বিপদও: পর্ব ১২-এর off-by-one বাগ ঠিক এখান থেকেই NaN বানায়।
length — শুধু পড়ার নয়, লেখারও
length সবসময় “সবচেয়ে বড় index + 1”. এটি নিজে থেকে বাড়ে, আর আপনি নিজেও এতে মান বসাতে পারেন:
const a = [1, 2, 3, 4, 5];
a.length = 2; // a → [ 1, 2 ] — শেষের তিনটি মুছে গেল
a[5] = 9; // a → [ 1, 2, <3 empty items>, 9 ], length 6
a.length = 0; // a → [] — array খালি করার চেনা কৌশল
length ছোট করলে বাড়তি উপাদান সত্যিই মুছে যায় — ফেরত আনা যায় না। আর সীমার অনেক বাইরে লিখলে মাঝখানে hole তৈরি হয়। length-এর সর্বোচ্চ মান 232 − 1 (৪২৯ কোটির কিছু বেশি); তার বেশি চাইলে RangeError: Invalid array length.
at() — শেষ থেকে গোনা
শেষ উপাদান চাই? পুরোনো উপায় arr[arr.length - 1] — লম্বা আর ভুলপ্রবণ। Python-এর অভ্যাসে arr[-1] লিখলে আসে undefined, কারণ JavaScript-এ -1 একটি সাধারণ property-নাম — arr[-1] = 'x' লিখলে "-1" নামের একটি property তৈরি হয়, length বদলায় না। তাই ES2022-এ যুক্ত হয়েছে at(): [10, 20, 30].at(-1) দেয় 30, at(-2) দেয় 20, at(0) দেয় 10. String-এও at() আছে।
Hole আর delete — কেন delete arr[i] নয়
Array থেকে একটি উপাদান মুছতে delete arr[1] লিখলে উপাদানটি যায়, কিন্তু ঘরটি থেকে যায় ফাঁকা হয়ে: [1, 2, 3] হয় [ 1, <1 empty item>, 3 ], length তখনো 3. পরের উপাদানগুলো সরে আসে না। ফাঁকা ঘর পড়লে undefined আসে, কিন্তু forEach সেটি টপকে যায়, for…of টপকায় না — একেক জায়গায় একেক আচরণ। উপাদান মুছতে হলে splice বা toSpliced (পরের দুই অংশ)। আধুনিক engine hole-যুক্ত array-কে ধীর পথে চালায় (ভাবনার প্রশ্ন ১), তাই hole এড়িয়ে চলাই নিয়ম।
৪ · যোগ আর বাদ — push, pop, shift, unshift, splice, slice
একটি অনলাইন দোকানের কার্ট আর একটি ডাক্তারের চেম্বারের অপেক্ষার তালিকা — দুটোই array, কিন্তু কাজের ধরন আলাদা। কার্টে শেষে পণ্য যোগ হয়, যেকোনো জায়গা থেকে বাদ যায়। অপেক্ষার তালিকায় নতুন রোগী দাঁড়ান পেছনে, ডাক পান সামনের জন। প্রতিটি কাজের জন্য আলাদা method:
| Method | কাজ | ফেরত দেয় | Array বদলায়? |
|---|---|---|---|
push(x, …) | শেষে যোগ | নতুন length | হ্যাঁ |
pop() | শেষেরটি বাদ | বাদ পড়া উপাদান (খালি হলে undefined) | হ্যাঁ |
unshift(x, …) | শুরুতে যোগ | নতুন length | হ্যাঁ |
shift() | প্রথমটি বাদ | বাদ পড়া উপাদান | হ্যাঁ |
splice(start, deleteCount, …items) | যেকোনো জায়গায় বাদ ও যোগ | বাদ পড়া উপাদানের array | হ্যাঁ |
slice(start, end) | একটি অংশের কপি (end বাদে) | নতুন array | না |
concat(…) | জোড়া লাগানো | নতুন array | না |
fill(v, start, end) | একটি অংশ একই মানে ভরা | সেই array-ই | হ্যাঁ |
লক্ষ করুন push নতুন array নয়, নতুন দৈর্ঘ্য ফেরত দেয়। তাই cart = cart.push('চিনি') লিখলে cart হয়ে যায় একটি সংখ্যা — চেনা ভুল।
শেষ প্রান্ত দ্রুত, শুরুর প্রান্ত ধীর
push/pop শুধু শেষ প্রান্তে কাজ করে — অন্য কোনো উপাদান সরাতে হয় না। এই জোড়া দিয়ে একটি stack (শেষে ঢোকে, শেষেরটা আগে বেরোয়) বানানো যায়। unshift/shift শুরুতে কাজ করে, তাই বাকি সব উপাদানের index এক ঘর সরাতে হয় — তালিকা যত বড়, তত বেশি কাজ (সাধারণভাবে O(n)). অপেক্ষার তালিকা — একটি queue — push দিয়ে পেছনে যোগ আর shift দিয়ে সামনের জনকে ডাকা — ছোট তালিকায় একদম ঠিক। Engine কিছু ক্ষেত্রে shift-কে কৌশলে দ্রুত করে, কিন্তু লাখো উপাদানের queue-তে তার ওপর ভরসা না করে সামনের index একটি আলাদা variable-এ রাখুন, বা একটি আসল queue কাঠামো ব্যবহার করুন (DSA কোর্সে বিস্তারিত)।
splice — যেকোনো জায়গায় কাটা আর বসানো
splice array-র সবচেয়ে শক্তিশালী — আর সবচেয়ে ভুল-বোঝা — method. তিন অংশ: কোথা থেকে (start), কয়টি বাদ (deleteCount), আর সেখানে কী বসবে (...items). একটি বাসের সারির আসন-কোড দিয়ে ধাপে ধাপে দেখুন:
1const row = ['A', 'B', 'C', 'D', 'E'];2const out = row.splice(1, 2, 'X', 'Y', 'Z');3console.log(row, out);
| এখন যা হচ্ছে | |
| row | |
| row.length | |
| out |
- শুরুর অবস্থা। পাঁচটি আসন-কোড, index 0 থেকে 4.
length5. start= 1. কাজ শুরু হবে index 1 থেকে —'B'-এর ঘরে।startঋণাত্মক হলে শেষ থেকে গোনা হয়:splice(-1)মানে শেষ ঘর থেকে।deleteCount= 2. index 1 আর 2 —'B'আর'C'— কেটে একটি নতুন array-তে রাখা হলো। এটিই হবে ফেরত মান।deleteCountনা দিলেstartথেকে শেষ পর্যন্ত সব কাটা হয়; 0 দিলে কিছুই কাটা হয় না — শুধু বসানো।- items বসানো। ফাঁকা হলো দুটি ঘর, কিন্তু বসবে তিনটি —
'X','Y','Z'. তাই'D'আর'E'এক ঘর ডানে সরল।length5 থেকে 6. এই সরানোর কাজের জন্যই শুরুর দিকেspliceবড় array-তে ধীর। - ফেরত মান — বাদ পড়াগুলো।
spliceনতুন array নয়, কাটা উপাদানগুলোর array ফেরত দেয়:['B', 'C']. আর আসলrowনিজেই বদলে গেছে।const updated = row.splice(…)লিখে “আপডেট হওয়া তালিকা” আশা করা খুব চেনা ভুল। - আউটপুট।
[ 'A', 'X', 'Y', 'Z', 'D', 'E' ] [ 'B', 'C' ]. একই কাজ আসলটি না বদলে চাইলে:row.toSpliced(1, 2, 'X', 'Y', 'Z')— নতুন array ফেরত দেয়, কিন্তু কাটা উপাদানগুলো দেয় না।
slice — নাম কাছাকাছি, কাজ উল্টো
slice(start, end) একটি অংশের কপি দেয়, আসল array ছোঁয় না। end-এর ঘরটি বাদ থাকে: [1, 2, 3, 4].slice(1, 3) দেয় [ 2, 3 ]. ঋণাত্মক index চলে — slice(-2) দেয় শেষ দুটি। কোনো argument না দিলে slice() পুরো array-র কপি। মনে রাখার কৌশল: splice-এ বাড়তি “p” — সে array-তে হাত দেয় (permanent change). আর concat কয়েকটি array জোড়া লাগিয়ে নতুন array দেয়: [5, 1].concat([7], 8) → [ 5, 1, 7, 8 ]; আধুনিক কোডে একই কাজ প্রায়ই spread দিয়ে — [...a, ...b] (পর্ব ১৭)।
৫ · বদলায়, নাকি নতুন দেয় — sort বনাম toSorted আর ES2023-এর কপি-method
ধরুন একটি দোকানের অ্যাপে পণ্যের দামের তালিকা আছে, যোগ করার ক্রমে। এক জায়গায় তালিকাটি যেমন আছে তেমন দেখাতে হবে, আরেক জায়গায় কম দাম থেকে বেশি। সাজাতে sort() ডাকলেন — আর হঠাৎ প্রথম জায়গার ক্রমও বদলে গেল। কেন? ধাপে ধাপে মেমরির ছবিতে দেখুন:
const prices = [320, 145, 199]; const shown = prices; // "যোগ করার ক্রমে" দেখানোর তালিকা variable heap (আসল array) prices ──┐ ├──▶ #1 [320, 145, 199] shown ──┘
prices.sort();
prices ──┐
├──▶ #1 [145, 199, 320] ← নতুন array নয়, #1 নিজেই
shown ──┘
prices.sort() === prices // true — সেই একই array ফেরত
console.log(shown); [ 145, 199, 320 ] // "যোগ করার ক্রমে" তালিকা আর যোগ করার ক্রমে নেই। // কোনো error নেই, কোনো সতর্কবার্তা নেই।
// আবার শুরু থেকে: prices = shown = #1 [320, 145, 199] const cheapFirst = prices.toSorted(); prices ──┐ ├──▶ #1 [320, 145, 199] ← অক্ষত shown ──┘ cheapFirst ──▶ #2 [145, 199, 320] ← নতুন
বদলায় (in place) নতুন array দেয় (ES2023) arr.sort() arr.toSorted() arr.reverse() arr.toReversed() arr.splice(i, n, …) arr.toSpliced(i, n, …) arr[i] = v arr.with(i, v)
- দুই নাম, একটি array.
const shown = pricesকপি বানায়নি — দুই variable-এ একই array-র reference (পর্ব ১৪-এর object-এর মতোই)। Heap-এ array একটিই, নাম #1. sort()জায়গায় বসেই সাজায়। In place মানে নতুন array নয়, #1-এর ভেতরের মানগুলোই জায়গা বদলাল। ফেরত দেয় সেই একই array — তাইconst sorted = prices.sort()লিখলেsortedআরpricesএকই জিনিস, “কপি” নয়।- যে অংশ #1 দেখছিল, সেও বদলে গেছে।
shownদিয়ে আঁকা তালিকা এখন সাজানো। বড় অ্যাপে এমন বাগ খুঁজে পাওয়া কঠিন — বদলটি হয়েছে অন্য ফাইলের অন্য function-এ। এটিই side effect-এর ক্লাসিক উদাহরণ। toSorted()নতুন array বানায়। #1 অক্ষত, সাজানো ফল #2-তে। যে আসল তালিকা দেখছিল, সে কিছুই টের পায় না। দাম: একটি বাড়তি array-র মেমরি — ছোট-মাঝারি তালিকায় নগণ্য।- চারটি জোড়া। ES2023-এ যুক্ত “change array by copy” method-গুলো প্রতিটি mutating কাজের একটি কপি-রূপ দেয়। এরা কখনো hole রাখে না — ফাঁকা ঘর
undefinedহয়ে যায়। Edge/Chrome 110 থেকে আর Node 20-এ আছে; খুব পুরোনো browser সমর্থন করতে হলে[...arr].sort()একই কাজ করে।
sort() ডিফল্টে সংখ্যা নয়, লেখা হিসেবে সাজায়। [10, 1, 2].sort() দেয় [ 1, 10, 2 ] — কারণ মানগুলো string-এ বদলে অক্ষর ধরে তুলনা হয়, আর "10" শুরু হয় "1" দিয়ে। ওপরের দামগুলো সব তিন অঙ্কের বলে ঠিক এসেছে, ভাগ্যক্রমে। সংখ্যা সাজাতে তুলনার function লাগে: toSorted((a, b) => a - b). কীভাবে এটি কাজ করে আর বাংলা নাম কীভাবে সাজাবেন — পর্ব ১৬-এ।
এবার সব একসাথে চালিয়ে দেখুন — কার্টে যোগ-বাদ, সাজানো কপি, id দিয়ে একটি পণ্য বাদ দেওয়া, আর অপেক্ষার তালিকা:
const cart = ['চাল', 'ডাল', 'তেল'];
cart.push('চিনি'); // শেষে যোগ — cart বদলাল
const removed = cart.splice(1, 1); // index 1 থেকে ১টি বাদ
console.log(cart, removed);
const sorted = cart.toSorted(); // নতুন array — cart অক্ষত
console.log(cart, sorted, cart === sorted);
const items = [{ id: 7, name: 'চাল' }, { id: 9, name: 'তেল' }];
const i = items.findIndex(it => it.id === 9);
const next = items.toSpliced(i, 1); // id 9 ছাড়া নতুন array
console.log(items.length, next.length, next[0] === items[0]);
const queue = ['রহিম', 'করিম']; // চেম্বারের অপেক্ষার তালিকা
queue.push('সুমি'); // নতুন রোগী পেছনে
console.log(queue.shift(), queue); // সামনের জন ডাক পেলেনচার লাইন আউটপুট। প্রথম: [ 'চাল', 'তেল', 'চিনি' ] [ 'ডাল' ] — splice “ডাল” কেটে ফেরত দিয়েছে, cart নিজে বদলেছে। দ্বিতীয়: [ 'চাল', 'তেল', 'চিনি' ] [ 'চাল', 'চিনি', 'তেল' ] false — cart-এর ক্রম অক্ষত, সাজানো রূপ আলাদা array (বাংলা শব্দ এখানে Unicode code unit-এর ক্রমে সাজানো — অভিধানের ক্রম নয়, পর্ব ১৬ দেখুন)। তৃতীয়: 2 1 true — items-এ এখনো দুটি, নতুন array-তে একটি; আর true বলছে নতুন array-র ভেতরের object আর পুরোনোটির object একই — কপি হয়েছে শুধু তালিকা, পণ্যগুলো নয় (অংশ ৭)। চতুর্থ: রহিম [ 'করিম', 'সুমি' ] — shift সামনের জনকে ফেরত দিয়ে তালিকা থেকে সরিয়েছে। পরীক্ষা করুন: findIndex-এ এমন id দিন যা নেই (যেমন 5) — i হবে -1, আর toSpliced(-1, 1) চুপচাপ শেষ পণ্যটি বাদ দেবে! তাই বাদ দেওয়ার আগে সবসময় if (i !== -1) যাচাই করুন।
৬ · খোঁজা — indexOf, includes, find, some, every আর NaN-এর রহস্য
খোঁজার প্রশ্ন তিন রকম, আর প্রতিটির জন্য আলাদা method:
| প্রশ্ন | Method | ফেরত দেয় | না পেলে |
|---|---|---|---|
| এই মানটি আছে কি? | includes(v) | true/false | false |
| এই মানটি কোন index-এ? | indexOf(v), lastIndexOf(v) | প্রথম / শেষ index | -1 |
| শর্ত মেলে এমন প্রথমটি কে? | find(fn), findLast(fn) | উপাদানটি | undefined |
| শর্ত মেলে এমন প্রথমটি কোথায়? | findIndex(fn), findLastIndex(fn) | index | -1 |
| অন্তত একটি কি শর্ত মানে? | some(fn) | true/false | খালি array-তে false |
| সবগুলো কি শর্ত মানে? | every(fn) | true/false | খালি array-তে true |
প্রথম দলটি একটি নির্দিষ্ট মান খোঁজে। বাকিরা একটি function নেয় — যা প্রতিটি উপাদান দেখে true/false বলে। হ্যাঁ/না উত্তর দেওয়া এমন function-কে বলে predicate; এটি একটি callback, যাকে method নিজে প্রতিটি উপাদানের জন্য ডাকে। find, some, every উত্তর পাওয়ামাত্র থামে — বাকি উপাদান দেখে না। findLast আর findLastIndex ES2023-এ যুক্ত — শেষ থেকে খোঁজে। Callback-নির্ভর method-এর পুরো জগৎ (map, filter, reduce) পর্ব ১৬-এ।
খেয়াল করুন: every খালি array-তে true দেয় — “খালি ক্লাসের সবাই পাস করেছে” কথাটি গণিতের ভাষায় সত্য, কারণ ফেল করা কাউকে দেখানো যায় না। এতে বাস্তব বাগ হয়: “সব পণ্য স্টকে আছে?” প্রশ্নে খালি কার্টও true পায়।
indexOf কেন NaN খুঁজে পায় না
পর্ব ১০-এ দেখেছেন, NaN === NaN হলো false — NaN নিজের সমানও নয়। indexOf তুলনা করে === দিয়ে, তাই [NaN].indexOf(NaN) দেয় -1. ES2016-এ আসা includes অন্য একটি তুলনা ব্যবহার করে — SameValueZero, যেখানে NaN নিজের সমান — তাই [NaN].includes(NaN) হলো true. সেন্সর রিডিং বা হিসাবের ফলের তালিকায় “কোনো ভুল (NaN) মান আছে কি?” জানতে তাই includes, বা findIndex(Number.isNaN). দুই তুলনার এই অদ্ভুত সহাবস্থানের ইতিহাস ভাবনার প্রশ্ন ৩-এ।
Array.isArray — array কি না, নিশ্চিত হওয়ার একমাত্র ভরসাযোগ্য উপায়
typeof [] দেয় 'object' — কোনো কাজের নয়। arr instanceof Array সাধারণত চলে, কিন্তু অন্য iframe-এ তৈরি array-তে false দেয়, কারণ প্রতিটি iframe-এর নিজের আলাদা Array আছে। ES5 (২০০৯) থেকে আছে Array.isArray(x) — সব ক্ষেত্রে ঠিক উত্তর দেয়।
এবার ক্লাসের নম্বরের তালিকায় খোঁজা চালিয়ে দেখুন — লুপ দিয়ে টপার, মান খোঁজা, শর্তে খোঁজা আর NaN-এর পার্থক্য:
const marks = [72, 88, 65, 88, 91, 59];
let best = 0; // টপারের index
for (let i = 1; i < marks.length; i++) {
if (marks[i] > marks[best]) best = i;
}
console.log('টপার: রোল', best + 1, '— নম্বর', marks[best]);
console.log(marks.indexOf(88), marks.lastIndexOf(88), marks.includes(100));
console.log(marks.find(m => m < 60), marks.findIndex(m => m > 90));
console.log(marks.findLast(m => m > 80), marks.some(m => m < 33), marks.every(m => m >= 33));
const readings = [12, NaN, 15]; // সেন্সরের রিডিং
console.log(readings.indexOf(NaN), readings.includes(NaN));
console.log(Array.isArray(marks), typeof marks);প্রথম লাইন: টপার: রোল 5 — নম্বর 91 — লুপ প্রতিটি নম্বরকে এখন পর্যন্ত সর্বোচ্চটির সাথে মেলায়, আর মান নয়, index মনে রাখে, যাতে রোলও জানা যায় (index 4 → রোল 5). দ্বিতীয়: 1 3 false — 88 প্রথমে index 1-এ, শেষে index 3-এ; 100 নেই। তৃতীয়: 59 4 — 60-এর নিচে প্রথম নম্বর 59, আর 90-এর বেশি প্রথমটি index 4-এ। চতুর্থ: 91 false true — শেষ থেকে খুঁজলে 59 বাদ পড়ে, 80-এর বেশি প্রথমে মেলে index 4-এর 91; কেউ 33-এর নিচে নয়, তাই সবাই পাস। পঞ্চম: -1 true — indexOf NaN পায় না, includes পায়। শেষ: true object. পরীক্ষা করুন: টপার খুঁজতে marks.indexOf(Math.max(...marks)) লিখেও একই উত্তর পাবেন — কিন্তু এটি array দুবার ঘোরে, আর দুজনের সমান সর্বোচ্চ হলে শুধু প্রথমজনকে দেয়। “সমান হলে কে টপার?” — এই প্রশ্নের উত্তর কোডে স্পষ্ট থাকা উচিত। আর একটি মজার তথ্য: variable-টির নাম top রাখলে browser-এর সাধারণ script-এর একদম বাইরের স্তরে SyntaxError: Identifier 'top' has already been declared আসে — কারণ browser-এর global-এ window.top আগে থেকেই আছে (Node-এ এই সমস্যা নেই)।
৭ · array একটি reference — ভাগাভাগি, === আর কপি
পর্ব ১৪-এ object-এর ক্ষেত্রে দেখেছেন, variable-এ object নিজে থাকে না, থাকে তার reference — heap-এ রাখা আসল জিনিসের ঠিকানা। Array-ও object, তাই একই নিয়ম। এর সবচেয়ে চমকে দেওয়া ফল: = দিয়ে “কপি” করলে আসলে কপি হয় না। ধাপে ধাপে দেখুন:
1const a = ['চা'];2const b = a;3b.push('বিস্কুট');4const c = [...a];5c.push('কেক');6console.log(a, c, a === b, a === c);
| a | |
| b | |
| c | |
| heap #1 | |
| heap #2 |
- Array তৈরি। Heap-এ একটি array (#1) জন্মাল, আর
a-তে রাখা হলো তার ঠিকানা। const b = a— কপি নয়। শুধু ঠিকানাটি কপি হলো। এখন দুটি নাম, একটি array — চায়ের দোকানের একটি খাতা, যেটি দুজন কর্মচারী ব্যবহার করেন।bদিয়ে বদলালেন,a-ও বদলাল।b.push#1-কে বদলাল।aসেই #1-ই দেখায়, তাইa-তেও এখন “বিস্কুট”. লক্ষ করুনconstএটি আটকায় না —constশুধু variable-কে অন্য array-তে সরতে দেয় না, array-র ভেতর বদলানো আটকায় না।[...a]— আসল কপি। Spread #1-এর প্রতিটি উপাদান নিয়ে একটি নতুন array (#2) বানাল। একই কাজ করেa.slice()আরArray.from(a).cবদলালেaঅক্ষত। “কেক” গেল শুধু #2-তে। দুই খাতা এখন আলাদা।===ঠিকানা মেলায়, মান নয়।a === bহলোtrue— একই array.a === cহলোfalse— ভেতরের মান এই মুহূর্তে এক হলেও ভিন্ন array. তাই[1, 2] === [1, 2]-ওfalse. মান মেলাতে হলে উপাদান ধরে ধরে তুলনা করতে হয়।
কপি কতটা গভীর? — shallow copy
[...a], slice(), Array.from(a), এমনকি toSorted() — সবাই shallow copy বানায়: শুধু উপরের স্তরের তালিকাটি নতুন, ভেতরের object বা array আগের মতোই ভাগাভাগি। const m = [[1, 2], [3, 4]]; const c = [...m]; c[0].push(9); — এরপর m হয় [ [ 1, 2, 9 ], [ 3, 4 ] ] (Node-এ চালিয়ে দেখা)। অংশ ৫-এর কোডে next[0] === items[0] সত্য হওয়ার কারণও এটাই। পুরো গভীর কপির উপায় structuredClone — পর্ব ১৯-এ।
Function-কে দেওয়া array বদলাবেন না। function addItem(cart, item) { cart.push(item); return cart; } — এটি ডাকলে যে ডাকল, তার array-ও বদলে যায়, কারণ function পেয়েছে একই reference. নিরাপদ রূপ: return [...cart, item]; — নতুন array ফেরত দিন, যে ডেকেছে সে ঠিক করবে পুরোনোটি রাখবে কি না। React-এর মতো framework-এ state হিসেবে রাখা array সরাসরি বদলালে পাতা নতুন করে আঁকাই হয় না, কারণ framework reference মিলিয়ে দেখে “কিছু বদলেছে কি?” (পর্ব ৫৪)। এ জন্যই ES2023-এর কপি-method এত জনপ্রিয় হয়েছে।
৮ · join, flat, 2D array, array-like আর typed array
Array থেকে লেখা — join আর toString
join(sep) সব উপাদান string-এ বদলে মাঝে sep বসিয়ে জোড়া দেয়: ['চাল', 'ডাল'].join(', ') → 'চাল, ডাল'. sep না দিলে কমা। null আর undefined হয়ে যায় খালি লেখা: [1, null, undefined, 2].join('-') → '1---2'. toString() হলো join(',')-এর মতো, আর template literal বা +-এ array বসালে এটিই ডাকা হয় — তাই [] + [] হলো খালি string (পর্ব ০৬-এর coercion).
ভাঁজ খোলা — flat আর flatMap
Array-র ভেতরে array থাকলে flat() এক স্তর খুলে দেয়: [1, [2, [3, [4]]]].flat() → [ 1, 2, [ 3, [ 4 ] ] ]; সব স্তর খুলতে flat(Infinity) → [ 1, 2, 3, 4 ]. flatMap(fn) প্রতিটি উপাদান থেকে একটি array বানিয়ে এক স্তর খুলে দেয় — যেমন প্রতিটি অর্ডার থেকে তার পণ্যগুলোর একটি লম্বা তালিকা। দুটিই ES2019 থেকে, আর নতুন array দেয়। flatMap ভালো করে বুঝতে map জানা দরকার — পর্ব ১৬।
2D array — বাসের আসন-নকশা
একটি দূরপাল্লার বাসে সারি আর কলামে আসন। প্রতিটি সারি একটি array, আর পুরো বাস সারিগুলোর array — একে বলে multidimensional array. seats[r][c] মানে আগে সারি, তারপর কলাম:
// null = খালি আসন, নাম = বুক করা
const seats = [
['রহিম', null, 'সুমি'], // সারি 0 (A)
[null, 'করিম', null ], // সারি 1 (B)
];
seats[1][0] = 'নীলা'; // B1 আসন বুক
const free = seats.flat().filter(s => s === null).length;
console.log(free); // প্রত্যাশিত আউটপুট: 2
// নতুন খালি বাস: ১০ সারি × ৪ কলাম — প্রতিটি সারি আলাদা array
const bus = Array.from({ length: 10 }, () => new Array(4).fill(null));শেষ লাইনটি লক্ষ করুন: new Array(10).fill(new Array(4).fill(null)) লিখলে দশটি সারিই একই array হতো — একজন A1 বুক করলে সব সারির প্রথম আসন বুক দেখাত (অংশ ২-এর সতর্কবার্তা)। গণিতের matrix, খেলার বোর্ড (দাবা, লুডু), বা spreadsheet-এর ঘর — সব একই ছকে। অনেক বড় সংখ্যার matrix-এ (ছবি প্রক্রিয়া, মেশিন লার্নিং) array-র ভেতরে array ধীর; সেখানে একটি লম্বা typed array-তে r * cols + c index ব্যবহার করা হয় — নিচে দেখুন।
Array-র মতো, কিন্তু array নয় — array-like
কিছু জিনিসের length আর সংখ্যা-index আছে, কিন্তু array-র method নেই — একে বলে array-like. দুটি চেনা উদাহরণ: পুরোনো ধাঁচের function-এর arguments object (পর্ব ১৩), আর পাতা থেকে খুঁজে পাওয়া element-এর তালিকা NodeList (document.querySelectorAll('li'), পর্ব ৩৭)। Array.isArray(arguments) হলো false, তাই arguments.includes নেই। সমাধান — Array.from(x) বা [...x] দিয়ে আসল array বানানো, তারপর যা খুশি। Array.from এমন object-ও নেয় যা iterable নয়, শুধু length আছে — অংশ ২-এর { length: 5 } কৌশল এই কারণেই কাজ করে।
Typed array — এক টাইপের সংখ্যা, নির্দিষ্ট আকার, দ্রুত
সাধারণ array যেকোনো কিছু রাখে, বাড়ে-কমে — এই নমনীয়তার দাম গতি আর মেমরি। ছবি, অডিও, ফাইলের bytes, 3D গ্রাফিক্স বা WebAssembly-র মতো কাজে লাগে লাখো একই ধরনের সংখ্যা, পাশাপাশি মেমরিতে। এর জন্য typed array: Uint8Array (০–২৫৫), Int32Array, Float64Array ইত্যাদি। এদের আকার স্থির, প্রতিটি ঘরে নির্দিষ্ট টাইপ — new Uint8Array([300, 2])-এ 300 ঢোকে না, মোড়ানো হয়ে 44 হয় (300 − 256). ভেতরের কাঁচা মেমরিটির নাম ArrayBuffer. প্রতিদিনের অ্যাপে এদের দরকার কম, কিন্তু Node-এর Buffer (পর্ব ৪৯), fetch-এ ফাইল নামানো বা canvas-এর pixel — সবখানে এরাই। ইতিহাস ভাবনার প্রশ্ন ৪-এ।
spliceআরslice-এর পার্থক্য? — splice আসল array বদলায় আর বাদ পড়া উপাদানের array ফেরত দেয়; slice বদলায় না, একটি অংশের নতুন কপি দেয়।[NaN].indexOf(NaN)আর[NaN].includes(NaN)? —-1আরtrue; indexOf === ব্যবহার করে, includes SameValueZero.- একটি array কপি করবেন কীভাবে? —
[...a],a.slice()বাArray.from(a)— তিনটিই shallow; গভীর কপিতেstructuredClone. - একটি মান array কি না, কীভাবে বুঝবেন? —
Array.isArray(x);typeofদেয়'object'. - Array খালি করার উপায়? —
arr.length = 0(একই array খালি, সব reference-ধারী টের পায়) বাarr = [](নতুন array, তাইletলাগে; পুরোনোটি যাঁদের হাতে, তাঁরা পুরোনোটিই দেখেন)।
৯ · AI-এর যুগে — AI-এর লেখা array-কোড যাচাই
“কার্ট থেকে একটি পণ্য বাদ দেওয়ার function লিখে দাও” — AI মুহূর্তে লিখে দেবে, আর একটি সাধারণ পরীক্ষায় চলবেও। কিন্তু array-র বাগ প্রায়ই লুকিয়ে থাকে কে কোন array বদলাল সেই প্রশ্নে, আর সীমানায় — না-পাওয়া id, খালি তালিকা, NaN. পুরোনো টিউটোরিয়াল থেকে শেখা AI-এর চেনা ভুলগুলো:
| AI যা প্রায়ই দেয় | সমস্যা | যা চাইবেন |
|---|---|---|
argument হিসেবে পাওয়া array-তে push/sort/splice | যে ডাকল তার ডেটাও বদলে যায় | নতুন array ফেরত — [...a, x], toSorted, toSpliced |
arr.indexOf(NaN) বা x === NaN | সবসময় -1 / false | includes বা Number.isNaN |
new Array(n).map(...) | hole — callback চলেই না | Array.from({ length: n }, fn) |
delete arr[i] | hole থাকে, length একই | splice(i, 1) বা toSpliced(i, 1) |
findIndex-এর ফল যাচাই না করে splice(i, 1) | -1 হলে শেষ উপাদান মুছে যায় | if (i !== -1) যাচাই |
Array(n).fill([]) দিয়ে 2D array | সব সারি একই array | Array.from({ length: n }, () => []) |
সংখ্যার array-তে শুধু sort() | লেখার ক্রমে সাজায়: 10 আসে 2-এর আগে | toSorted((a, b) => a - b) |
AI-কে array-কোড লিখতে বললে “বদলানো চলবে কি না” আগেই বলে দিন, আর সীমানার ক্ষেত্রগুলো নিজেই দেখাতে বলুন। নিচের prompt কপি করে নিজের কাজে বদলে নিন:
AI-এর array-কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১JavaScript-এর array আসলে string-নামের property-ওয়ালা একটি object — তাহলে এটি ধীর হয় না কেন? V8-এর “elements kind” কী, আর hole কেন গতি কমায়?
১৯৯৫ সালে Brendan Eich প্রায় দশ দিনে Netscape-এর জন্য JavaScript-এর প্রথম রূপ বানান। সময় বাঁচাতে নকশা ছিল সরল: প্রায় সবকিছু object, আর object মানে নাম থেকে মানের একটি ঝুলি। Array আলাদা কোনো মেমরি-কাঠামো হলো না — হলো একটি object, যার property-র নাম "0", "1", "2", সাথে একটি বিশেষ length, যা সবচেয়ে বড় index-এর সাথে নিজে মিলে থাকে। আজও মানদণ্ডে array একটি “exotic object” — তার একমাত্র বিশেষত্ব এই length-এর নিয়ম। তাই arr["2"] আর arr[2] একই জিনিস, আর array-তে arr.extra = 1 বসানোও বৈধ।
কাগজে-কলমে এটি ধীর হওয়ার কথা — প্রতিবার index পড়তে একটি নাম-ভিত্তিক খোঁজ। কিন্তু আধুনিক engine মানদণ্ডের “আচরণ” মেনে চলে, ভেতরের “গঠন” নিজের মতো বানায়। V8 প্রতিটি array-র উপাদান রাখে আলাদা একটি পাশাপাশি মেমরি-অংশে, আর লিখে রাখে সেখানে কী ধরনের মান আছে — একে বলে elements kind. ২০১৭ সালে V8 দলের একটি লেখায় (Mathias Bynens, “Elements kinds in V8”) এর বিবরণ আছে: শুধু ছোট পূর্ণসংখ্যা থাকলে PACKED_SMI_ELEMENTS, দশমিক এলে PACKED_DOUBLE_ELEMENTS, যেকোনো কিছু এলে PACKED_ELEMENTS. প্রতিটির একটি HOLEY_ রূপ আছে, যদি array-তে ফাঁকা ঘর থাকে।
দুটি নিয়ম গুরুত্বপূর্ণ। প্রথম, রূপান্তর শুধু এক দিকে যায় — নির্দিষ্ট থেকে সাধারণে, packed থেকে holey-তে। একবার একটি string ঢুকলে বা একটি hole তৈরি হলে, সেটি সরিয়ে দিলেও array আর আগের দ্রুত শ্রেণিতে ফেরে না। দ্বিতীয়, holey array-তে প্রতিটি পড়ায় বাড়তি প্রশ্ন: “এই ঘরে কি সত্যিই কিছু আছে, নাকি prototype chain-এ খুঁজতে হবে?” — কারণ মানদণ্ড বলে hole পড়লে Array.prototype-ও দেখতে হয়। খুব বেশি ফাঁকা হলে (যেমন a[1000000] = 1 একটি খালি array-তে) engine array-টিকে “dictionary mode”-এ, অর্থাৎ সত্যিকারের নাম-ভিত্তিক hash table-এ নামিয়ে দিতে পারে। এই কারণেই new Array(n) দিয়ে শুরু করে ঘর ভরানো, বা delete arr[i] — দুটোই গতির দিক থেকে খারাপ অভ্যাস।
বাস্তব শিক্ষা: এক array-তে এক ধরনের মান রাখুন, ফাঁকা ঘর তৈরি করবেন না, আর literal বা Array.from দিয়ে শুরু থেকেই ভরা array বানান। তবে ছোট তালিকায় এই পার্থক্য চোখে পড়ে না — লাখো উপাদানের গরম লুপেই গুরুত্ব পায়।
মূল উপলব্ধি: ভাষার মানদণ্ড বলে কী দেখা যাবে, engine ঠিক করে কীভাবে রাখা হবে। যে কোড engine-এর অনুমান সহজ রাখে — এক টাইপ, কোনো ফাঁক নেই — সেটিই দ্রুত চলে।
প্র ০২sort() থাকতেও ES2023-এ toSorted() আনতে হলো কেন? “Change array by copy” প্রস্তাব আর React-এর মতো framework-এর immutability-র সম্পর্ক কী?
JavaScript-এর প্রথম দিকের array method-গুলো — sort, reverse, splice — নেওয়া হয়েছিল সেই সময়ের প্রচলিত ধারণা থেকে: মেমরি দামি, তাই জায়গায় বসেই বদলাও। ১৯৯০-এর দশকের ছোট স্ক্রিপ্টে এটি সমস্যা ছিল না। কিন্তু ২০১০-এর দশকে ওয়েব অ্যাপ হলো লক্ষ লাইনের, আর একই ডেটা প্রোগ্রামের দশ জায়গা থেকে দেখা হতে লাগল। তখন “কে কখন এই array বদলাল?” প্রশ্নটি হয়ে উঠল সবচেয়ে কঠিন বাগগুলোর উৎস।
উত্তর এলো functional programming-এর একটি পুরোনো নীতি থেকে: ডেটা বদলাবেন না, নতুন ডেটা বানান। ২০১৩ সালে Facebook-এর React এই ধারণাকে মূলধারায় আনে। React একটি component-কে নতুন করে আঁকবে কি না, তা ঠিক করতে পুরোনো আর নতুন state-এর reference মেলায় (Object.is). আপনি যদি একই array-তে push করে সেটিই আবার state-এ দেন, reference একই — React ভাবে কিছুই বদলায়নি, পাতা আঁকে না। তাই React-এ নিয়ম দাঁড়াল: setItems([...items, newItem]). Redux-এর মতো লাইব্রেরি নিয়মটিকে আরও কঠোর করল, আর Immer-এর মতো লাইব্রেরি এল, যা “বদলানোর মতো করে লিখুন, ভেতরে আমরা নতুন কপি বানাব” সুবিধা দেয়।
সমস্যা ছিল, ভাষা নিজে এই কাজ সহজ করত না। সাজানো কপি চাইলে [...arr].sort(), একটি ঘর বদলানো কপি চাইলে arr.map((x, i) => i === k ? v : x) — লম্বা আর ভুলের সুযোগে ভরা। Bloomberg-এর প্রকৌশলী Ashley Claymore আর Robin Ricard TC39-এ “Change Array by Copy” প্রস্তাব আনেন; এর বীজ ছিল অপরিবর্তনীয় Record ও Tuple নিয়ে আরেকটি বড় প্রস্তাবে। প্রস্তাবটি ES2023-এ মানদণ্ডে ঢোকে: toSorted, toReversed, toSpliced, with. Typed array-তেও এগুলোর তিনটি আছে — toSpliced বাদে, কারণ typed array-র দৈর্ঘ্য বদলায় না।
নাম বাছাইও শিক্ষণীয়। sorted() নয় কেন? ওয়েবে পুরোনো লাইব্রেরি নিজেরাই Array.prototype-এ method বসিয়েছিল, আর একই নামের নতুন method সেগুলো ভেঙে দিতে পারত। এর আগে এমন ঘটনা ঘটেছে: includes-এর প্রথম প্রস্তাবিত নাম ছিল contains, আর flat-এর ছিল flatten — দুটিই পুরোনো MooTools লাইব্রেরির সাথে সংঘাতের কারণে বদলাতে হয় (দ্বিতীয়টি ২০১৮ সালের মজার “SmooshGate” বিতর্ক)। “to” উপসর্গ পরিষ্কার একটি সংকেতও দেয়: এটি রূপান্তরিত নতুন কিছু দেবে, যেমন toString().
মূল উপলব্ধি: ভাষা বিবর্তিত হয় ব্যবহারকারীদের অভ্যাস দেখে। Immutability প্রথমে ছিল লাইব্রেরি আর নিয়মের ব্যাপার; যখন যথেষ্ট মানুষ একই কৌশল হাতে লিখছিলেন, তখন সেটি ভাষার অংশ হলো।
প্র ০৩indexOf আর includes দুই রকম সমতা ব্যবহার করে কেন? SameValueZero, ===, Object.is — একটি ভাষায় এতগুলো “সমান” কেন লাগল?
JavaScript-এর মানদণ্ডে আজ চারটি আলাদা সমতার নিয়ম আছে। == (loose — টাইপ বদলে তুলনা, পর্ব ০৬), === (strict), Object.is-এর নিয়ম (মানদণ্ডে নাম SameValue), আর SameValueZero. শেষ তিনটির পার্থক্য মাত্র দুটি বিশেষ মানে: NaN আর -0. ===-তে NaN নিজের সমান নয়, আর 0 === -0 সত্য। SameValue-তে NaN নিজের সমান, আর 0 ও -0 আলাদা। SameValueZero মাঝামাঝি: NaN নিজের সমান, কিন্তু 0 ও -0 সমান।
এই বিচিত্রতার মূলে ১৯৮৫ সালের IEEE 754 মানদণ্ড, যা প্রায় সব কম্পিউটারের দশমিক সংখ্যার নিয়ম (পর্ব ১০)। IEEE 754 বলে NaN কোনো কিছুর সমান নয়, নিজেরও নয় — যুক্তি হলো, “অসংজ্ঞায়িত ফল” দুটি একই কিনা বলা যায় না, আর x !== x দিয়ে NaN চেনা যায়। আর -0 আছে কারণ সংখ্যার চিহ্ন আলাদা bit-এ থাকে; গাণিতিক তুলনায় এটি 0-এর সমান হওয়াই স্বাভাবিক। === এই হার্ডওয়্যার-নিয়ম সরাসরি মানে।
কিন্তু “খোঁজা” আর “গাণিতিক তুলনা” এক কাজ নয়। আপনি যখন জিজ্ঞেস করেন “এই তালিকায় NaN আছে কি?”, তখন উত্তর “না” — যদিও আপনি নিজেই একটি NaN রেখেছিলেন — অর্থহীন। ES5 (২০০৯)-এর indexOf সোজা === ব্যবহার করেছিল, আর এই ফাঁদ তৈরি হলো। ES2015-এ যখন Map আর Set এল, TC39 বুঝল key হিসেবে NaN রাখা যাওয়া উচিত, আবার 0 আর -0-কে আলাদা key বানানো বিভ্রান্তিকর হবে। তাই তৈরি হলো SameValueZero. ES2016-এর includes একই নিয়ম নিল, যাতে set.has(x) আর arr.includes(x) একই উত্তর দেয়।
তাহলে indexOf-কেই ঠিক করা গেল না কেন? পুরোনো সব কোড indexOf-এর আচরণের ওপর দাঁড়িয়ে — “don't break the web” নীতি অনুযায়ী বিদ্যমান method-এর অর্থ বদলানো যায় না। তাই ভাষায় দুটি method পাশাপাশি থাকল, আর আপনার দায়িত্ব হলো কোনটি কোন নিয়মে চলে তা জানা। মজার ব্যাপার: [-0].includes(0) আর [-0].indexOf(0) দুটোই মেলে (যথাক্রমে true আর 0), পার্থক্য শুধু NaN-এ।
মূল উপলব্ধি: “সমান” একটি প্রসঙ্গ-নির্ভর প্রশ্ন — গাণিতিক তুলনা, পরিচয় আর সংগ্রহে খোঁজা আলাদা উত্তর চায়। কোনো তুলনা লেখার আগে জানুন কোন নিয়ম চলছে, বিশেষ করে NaN আর -0 আসতে পারে এমন ডেটায়।
প্র ০৪Typed array আর ArrayBuffer কেন এলো? একটি নমনীয় ভাষায় “স্থির আকার, এক টাইপ”-এর array রাখার পেছনে কোন ইতিহাস আর প্রয়োজন?
২০১০ সালের আশপাশে browser-এ 3D গ্রাফিক্স আনার কাজ চলছিল — WebGL, যা OpenGL ES-এর ওপর দাঁড়ানো এবং Khronos Group-এর তত্ত্বাবধানে তৈরি। WebGL 1.0 মানদণ্ড প্রকাশিত হয় ২০১১ সালে। সমস্যা দাঁড়াল: GPU-কে লাখো vertex-এর স্থানাঙ্ক পাঠাতে হয় পাশাপাশি সাজানো ৩২-bit দশমিক সংখ্যা হিসেবে। সাধারণ JavaScript array-তে প্রতিটি মান একটি সাধারণ JavaScript মান — টাইপ-তথ্যসহ, হয়তো heap-এ আলাদা জায়গায়। প্রতিবার GPU-তে পাঠানোর আগে রূপান্তর করতে হলে গতি আর থাকে না।
সমাধান হিসেবে WebGL-এর সাথে আসে Typed Array মানদণ্ড: ArrayBuffer হলো কাঁচা bytes-এর একটি নির্দিষ্ট আকারের খণ্ড, আর তার ওপর বিভিন্ন “দৃষ্টি” — Float32Array, Uint8Array, Int16Array ইত্যাদি — যা একই bytes-কে বিভিন্ন টাইপের সংখ্যা হিসেবে পড়ে। DataView দিয়ে byte-স্তরে মিশ্র কাঠামো পড়া যায়, যেমন একটি ফাইলের header. প্রথমে এটি browser-এর আলাদা API ছিল; ES2015 এদের ভাষার মূল মানদণ্ডে নিয়ে আসে।
এরপর এদের ব্যবহার গ্রাফিক্সের বাইরে ছড়িয়ে পড়ে। Canvas-এর pixel ডেটা থাকে Uint8ClampedArray-এ (যা ২৫৫-এর বেশি মানকে মোড়ায় না, ২৫৫-তে আটকে রাখে)। Web Audio-র শব্দ-নমুনা Float32Array. fetch দিয়ে আনা ফাইল arrayBuffer() দিয়ে পড়া যায়। আজকের Node-এ Buffer নিজেই Uint8Array-এর একটি subclass. আর WebAssembly-র পুরো মেমরি JavaScript-এর দিক থেকে একটি ArrayBuffer — দুই জগতের মধ্যে সেতু।
একটি নাটকীয় অধ্যায়ও আছে। ES2017-এ এসেছিল SharedArrayBuffer — একাধিক Worker একই মেমরি ভাগ করতে পারে। ২০১৮ সালের জানুয়ারিতে প্রকাশিত Spectre নামের CPU-দুর্বলতার পর browser-গুলো এটি সাময়িকভাবে বন্ধ করে দেয়, কারণ এটি দিয়ে খুব সূক্ষ্ম টাইমার বানানো যেত। পরে “cross-origin isolation” নামে কঠোর নিরাপত্তা-শর্ত মানা পাতার জন্য এটি আবার চালু হয় (পর্ব ৪২)। আর ES2024-এ এসেছে আকার বদলানো যায় এমন (resizable) ArrayBuffer.
বিনিময়টি পরিষ্কার: typed array বাড়ে-কমে না, মিশ্র টাইপ রাখে না, সীমার বাইরের মান নিঃশব্দে মোড়ায় বা কাটে — কিন্তু মেমরি অনুমানযোগ্য, গতি C-র কাছাকাছি, আর বাইরের জগতের (GPU, ফাইল, নেটওয়ার্ক) সাথে কপি ছাড়াই কথা বলা যায়।
মূল উপলব্ধি: একটি নমনীয় ভাষাও যখন হার্ডওয়্যারের কাছে যেতে চায়, তখন নমনীয়তা ছেড়ে অনুমানযোগ্যতা বেছে নেয়। সাধারণ array মানুষের জন্য, typed array যন্ত্রের জন্য।
প্র ০৫index কেন 0 থেকে শুরু, 1 থেকে নয়? Dijkstra-র ১৯৮২ সালের যুক্তি কী ছিল, আর at(-1) সেই গল্পে কোথায় বসে?
দৈনন্দিন জীবনে আমরা ১ থেকে গুনি — প্রথম রোল ১, প্রথম তলা (বাংলাদেশে) নিচতলার ওপরেরটি। তাহলে বেশিরভাগ প্রোগ্রামিং ভাষা 0 থেকে কেন? একটি কারণ যান্ত্রিক। C আর তার পূর্বসূরি BCPL (Martin Richards, ১৯৬০-এর দশকের শেষ দিক)-এ array-র নাম আসলে প্রথম ঘরের ঠিকানা, আর a[i] মানে “শুরু থেকে i ঘর দূরে”। প্রথম ঘর শুরু থেকে 0 ঘর দূরে — তাই index 0. C-তে a[i] আক্ষরিক অর্থেই *(a + i).
কিন্তু সবচেয়ে বিখ্যাত যুক্তিটি গাণিতিক। ১৯৮২ সালের আগস্টে Edsger W. Dijkstra হাতে লেখা একটি ছোট নোট লেখেন — EWD831, শিরোনাম “Why numbering should start at zero”. তাঁর প্রশ্ন: ২, ৩, …, ১২ — এই সংখ্যার পরিসর লেখার সবচেয়ে ভালো উপায় কী? চারটি বিকল্প: 2 ≤ i < 13, 1 < i ≤ 12, 2 ≤ i ≤ 12, 1 < i < 13. তিনি দেখান প্রথমটি — নিচের সীমা অন্তর্ভুক্ত, ওপরেরটি বাদ (half-open) — সবচেয়ে সুবিধাজনক: পরিসরের দৈর্ঘ্য সরাসরি 13 − 2; পাশাপাশি দুটি পরিসর জোড়া লাগে কোনো ফাঁক বা দ্বিগুণ ছাড়া ([a, b) আর [b, c)); আর খালি পরিসর লেখা যায় a ≤ i < a. এখন N উপাদানের array-র index লিখতে হলে, 1 থেকে শুরু করলে 1 ≤ i < N + 1, আর 0 থেকে করলে 0 ≤ i < N — দ্বিতীয়টিই পরিচ্ছন্ন।
এই পর্বেই দেখুন যুক্তিটি কোথায় কাজ করছে: slice(start, end) half-open — end বাদ। তাই slice(0, n) ঠিক n টি উপাদান দেয়, আর slice(0, k) ও slice(k) মিলে পুরো array, কোনো ফাঁক নেই। লুপের i < arr.length-ও এই একই ছক। সব ভাষা অবশ্য এই পথে যায়নি: Fortran, MATLAB, R, Lua 1 থেকে গোনে — গণিত আর বিজ্ঞানের ঐতিহ্য থেকে। Pascal আপনাকে নিজের সীমা বাছতে দিত।
আর at(-1)? শেষ থেকে গোনা Python-এ বহুদিন ধরে আছে (a[-1]). JavaScript এটি []-এ আনতে পারেনি, কারণ array একটি object আর "-1" একটি বৈধ property-নাম — অর্থ বদলালে পুরোনো কোড ভাঙত। তাই একটি method: প্রস্তাবটির প্রথম নাম ছিল item(), কিন্তু পুরোনো সাইটের সাথে সংঘাত ধরা পড়ায় নাম বদলে at() রাখা হয়, আর ES2022-এ এটি মানদণ্ডে আসে। লক্ষ করুন, শেষ থেকে গোনা শুরু হয় −1 থেকে, −0 থেকে নয় — কারণ at(-k) আসলে at(length − k).
মূল উপলব্ধি: 0 থেকে গোনা কোনো খেয়াল নয় — এটি half-open পরিসরের একটি সুসংগত ব্যবস্থার অংশ, যা slice, লুপ আর দৈর্ঘ্যের হিসাবকে off-by-one ভুল থেকে বাঁচায়। নিয়মটির যুক্তি বুঝলে সীমানার হিসাব মুখস্থ করতে হয় না।
অনুশীলন
বেশিরভাগ কাজ দেখা আর ভাবার — কোড লেখার কাজ মাত্র একটি।
-
ফল অনুমান করুন: চালানোর আগে বলুন প্রতিটি লাইনের পরে
aকী —const a = [1, 2, 3, 4];→ (ক)a.length = 2;(খ)a.push(5);(গ)const x = a.splice(0, 1);(ঘ)const y = a.slice(0, 1);— আরx,yকী?(ক)
[1, 2]—lengthছোট করায় 3, 4 মুছে গেছে। (খ)[1, 2, 5]—pushফেরত দিয়েছে 3 (নতুন দৈর্ঘ্য)। (গ)a=[2, 5],x=[1]—spliceবাদ পড়াগুলোর array দেয়, একটি হলেও array. (ঘ)aঅপরিবর্তিত[2, 5],y=[2]—sliceশুধু কপি দেয়। -
বাগ শিকারি: একজন লিখেছেন
const rolls = new Array(40).map((_, i) => i + 1);— উদ্দেশ্য ১ থেকে ৪০ রোলের তালিকা।rolls[0]কী আসবে, কেন, আর ঠিক করবেন কীভাবে?rolls[0]হলোundefined.new Array(40)শুধুlength40 বসায়, ঘরগুলো hole;maphole টপকে যায়, তাই callback একবারও চলেনি — ফল আবার ৪০টি hole. ঠিক রূপ:Array.from({ length: 40 }, (_, i) => i + 1). বিকল্প:new Array(40).fill(0).map((_, i) => i + 1)— আগে ভরা, তারপর map. -
কার array বদলাল?
function topThree(list) { return list.sort((a, b) => b - a).slice(0, 3); }—const marks = [70, 95, 60, 88];দিয়ে ডাকার পরmarksকী? সমস্যাটি কী, আর এক শব্দ বদলে কীভাবে ঠিক করবেন?ফল ঠিক আসে —
[95, 88, 70]— কিন্তুmarksনিজেই হয়ে যায়[95, 88, 70, 60].sortপাওয়া array-টিকেই সাজায়, আরlistওmarksএকই reference. রোল নম্বরের ক্রমে রাখা তালিকার ক্রম হারিয়ে গেল — এখনmarks[0]আর রোল ১-এর নম্বর নয়! ঠিক করতেsort-এর জায়গায়toSortedলিখুন:list.toSorted((a, b) => b - a).slice(0, 3). -
নিজে লিখুন (কোড লেখার কাজ): বাসের আসন-নকশা
const seats = [['রহিম', null], [null, null], ['সুমি', 'করিম']];(null = খালি)। প্রথম খালি আসনের সারি আর কলাম[r, c]আকারে বের করুন (সারি ধরে বাঁ থেকে ডানে খুঁজে); কোনো খালি না থাকলেnull.প্রতিটি সারিতে
indexOf(null)দিয়ে খুঁজুন, পাওয়ামাত্র ফেরত দিন:function firstFree(seats) { for (let r = 0; r < seats.length; r++) { const c = seats[r].indexOf(null); if (c !== -1) return [r, c]; } return null; } const seats = [['রহিম', null], [null, null], ['সুমি', 'করিম']]; console.log(firstFree(seats)); // [ 0, 1 ]প্রথম খালি আসন সারি 0-এর কলাম 1, অর্থাৎ A2.
indexOf(null)এখানে নিরাপদ, কারণnull === nullসত্য (NaN-এর মতো সমস্যা নেই)।-1যাচাই বাদ দিলে প্রথম সারিতেই[0, -1]ফেরত যেত — ভুল উত্তর।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমআসল array না বদলে id দিয়ে একটি উপাদান বাদ দেওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
findIndexদিয়ে id-র index বের করা- index
-1কি না যাচাই করা toSpliced(index, 1)দিয়ে নতুন array পাওয়া- নতুন array-টিকে তালিকার variable বা state-এ বসানো
- নতুন তালিকা দিয়ে পাতা আবার দেখানো
JavaScript-এ const a = [1, 2, 3]; const r = a.push(9); চলার পর r-এর মান কী?
[1, 2, 3, 9]- 9 — যোগ করা মান
- 4 — array-র নতুন length
- undefined
JavaScript-এ [1, NaN, 3].indexOf(NaN)-এর ফল কী?
- 1
- -1
- true
- TypeError
JavaScript-এ const a = ['x']; const b = a; b.push('y'); চলার পর a.length কত?
- 2
- 1
- TypeError, কারণ a একটি const
- 3
নিচের কোন JavaScript array method আসল array বদলায় না, নতুন array ফেরত দেয়?
sort()reverse()splice()toSorted()
JavaScript-এ const a = [10, 20, 30]; হলে a[-1] আর a.at(-1) যথাক্রমে কী দেয়?
30আর30- RangeError আর
30 - undefined আর
30 - undefined আর
10
JavaScript-এ new Array(3).map((_, i) => i)-এর ফল কী?
[0, 1, 2]- তিনটি ফাঁকা ঘর (hole)-এর array, length 3
[undefined, undefined, undefined]— তিনটি আসল undefined মান[3]
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Array = ক্রমে সাজানো তালিকা, index 0 থেকে; ভেতরে একটি object, তাই
typeofনয়,Array.isArray. শেষ উপাদানat(-1). - তৈরি করুন
[]বাArray.from({ length: n }, fn);new Array(n)hole দেয়,delete arr[i]-ও hole রাখে। - বদলায়: push, pop, shift, unshift, splice, sort, reverse, fill,
length =. নতুন দেয়: slice, concat, flat, toSorted, toReversed, toSpliced, with. - মান খুঁজতে
includes(NaN-ও পায়) /indexOf(=== দিয়ে); শর্তেfind/findIndex/findLast; হ্যাঁ/না-তেsome/every. “না পেলে”-1/undefinedসামলান। const b = aকপি নয়;[...a]কপি, কিন্তু shallow. Function-এ পাওয়া array বদলাবেন না — নতুন array ফেরত দিন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৬ · map, filter, reduce আর সাজানোপরবর্তী পর্বCallback দিয়ে array রূপান্তর, ছাঁকা, যোগফল আর ঠিকভাবে সাজানো।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python প্রোগ্রামিং · listতুলনা করুনJavaScript array-র সবচেয়ে কাছের আত্মীয় — negative index, slicing আর কপি।
- Data Structures & Algorithmsগভীরে যানArray, stack আর queue-র খরচ — কোন কাজ কেন ধীর আর কীভাবে দ্রুত করবেন।