Learn JavaScript

lesson ২ / ৯ · string আর template literal

Module ৩ · string আর template literal

প্রতিটা string method, একটা একটা করে

Freeপড়া

এই lesson-এ যা শিখবে

  • একজন programmer কাজে যত string method ব্যবহার করে, at থেকে localeCompare পর্যন্ত, সবগুলো call করতে পারবে, আর কোনটা কী ফেরত দেবে আগেই বলতে পারবে।
  • প্রতিটা method-এর খরচ O(1), O(k), O(n) না O(n + k), বলতে পারবে, আর কোন কাজের জন্য ওই খরচ, সেটাও।
  • if (s.indexOf(x)) কেন bug, বুঝিয়ে বলতে পারবে, ASCII table থেকে যেকোনো character-এর code পড়তে পারবে, আর template literal দিয়ে output-এর লাইন বানাতে পারবে।

Zara-র online বইয়ের দোকানের search box বানাচ্ছে Bob। Reader যে শব্দ লেখে, কোনো title-এ সেটা থাকলেই title-টা মিলে যাবে। Bob Java দিয়ে test করে, আর Learn Java ঠিকঠাক পাওয়া যায়। তারপর bug report আসতে থাকে: Java for Kids নাকি "not found", আর বাকি সব title-এর প্রথম শব্দেরও একই দশা। Bob লিখেছিল if (title.indexOf(word)), আর লাইনটা কেন ভুল, সেটা বলবে এই lesson-এর search-এর অংশ।

Lesson 01-এ string দেখেছ বাক্সের একটা সারি হিসেবে: code unit-গুলো, 0 থেকে নম্বর দেওয়া। Method হলো একটা value-র নিজের function: dot দিয়ে call করো, যেমন s.trim()। এই lesson একজন programmer-এর কাজের প্রতিটা string method-এর কাছে যাবে, একেকবারে একেকটা পরিবার। প্রতিটা পরিবারের জন্য থাকবে তার গড়ন, ছোট একটা program, তার আসল output, আর তার খরচ।

এটা এই module-এর সবচেয়ে লম্বা lesson, চাইলে দুই বসায় পড়ো: split আর join হলো মাঝপথ। প্রতিটা পরিবার নিজে নিজেই দাঁড়ায়, আর রোজ যে ছয়টা টাইপ করবে সেগুলো slice, split, join, trim, includes আর template literal।

খরচের লাইন কীভাবে পড়বে

নিচের প্রতিটা পরিবারের শেষে এক সারির একটা table থাকবে: call-টা, তার খরচ (cost), আর কেন। খরচ বলে string বড় হলে কাজটা কীভাবে বাড়ে, কত millisecond লাগে সেটা না। এটা লেখা হয় big-O notation-এ, দুইটা অক্ষর দিয়ে:

  • n হলো যে string-এর উপর method call করছ, তার length, code unit-এ।
  • k হলো ফলের length, অথবা যে text খুঁজছ তার length।
খরচযেভাবে পড়বেউদাহরণ
O(1)length যত বড়ই হোক, একই অল্প কাজ5টা code unit-এর উপর s.length, বা 5 million-এর উপর
O(k)ফল যত বড়, কাজ ততs.slice(0, 3) copy করে 3টা code unit
O(n)পুরো string যত বড়, কাজ ততs.toUpperCase() প্রতিটা code unit-এ যায়
O(n + k)string-এর ভিতর দিয়ে একবার হাঁটা, সাথে খোঁজার text বা ফলs.indexOf(t) s-এর ভিতরে t খোঁজে

ECMAScript, মানে language-এর standard, বলে দেয় কোন method কী ফেরত দেবে, কত তাড়াতাড়ি সেটা বলে না। তাই খরচের প্রতিটা লাইন বলে কাজটা কত বড়, V8 যেভাবে করে; V8 হলো Node 22-এর ভিতরের engine। দুই জায়গায় V8 কম কাজ করে (লম্বা একটা slice, আর +), আর এক জায়গায় বেশি; ওই লাইনগুলোতে সেটা লেখা আছে। তাই O(n) পড়ো এভাবে: "string দ্বিগুণ হলে কাজও দ্বিগুণ"।

পুরো দলটা এক নজরে

এই lesson যত গড়ন শেখাবে, সব এক card-এ রইল। নিচে প্রতিটার নিজের অংশ আছে, ঠিক এই ক্রমে।

String method, পরিবার ধরে

একটা unit পড়া   s.length   s[i]   s.at(i)   s.charAt(i)
টুকরো কাটা      s.slice(start, end)   s.substring(start, end)
খোঁজা           s.indexOf(t)   s.lastIndexOf(t)   s.includes(t)
দুই মাথা        s.startsWith(t)   s.endsWith(t)
case            s.toUpperCase()   s.toLowerCase()
space ছাঁটা      s.trim()   s.trimStart()   s.trimEnd()
ভাঙা, জোড়া      s.split(sep)   parts.join(sep)
বদলানো          s.replace(a, b)   s.replaceAll(a, b)
লম্বা করা        s.padStart(w, fill)   s.padEnd(w, fill)   s.repeat(count)   s.concat(t)
code            s.charCodeAt(i)   s.codePointAt(i)   String.fromCharCode(code)
তুলনা           s < t   s > t   s.localeCompare(t)
বানানো          `text ${expression} text`
  • s যেকোনো string; t আরেকটা string; i, start, end, w আর count হলো number।
  • s.length-এ কোনো parentheses নেই: এটা জমা রাখা একটা value, একটা property, call করার মতো method না।

নিচের map একই method-গুলোকে সাজিয়েছে কে কী ফেরত দেয় সেই হিসেবে। উত্তরের type জানা থাকলে বুঝবে, এরপর ওটা দিয়ে কী করা যায়।

Method-এর map: কোন string method কী ফেরত দেয় কোন method কী ফেরত দেয় s.method(...) নতুন একটা string at(i) charAt(i) slice(a, b) substring(a, b) toUpperCase() toLowerCase() trim() trimStart() trimEnd() replace(a, b) replaceAll(a, b) padStart(w) padEnd(w) repeat(n) concat(t) String.fromCharCode(c) `... ${x} ...` একটা number length indexOf(t) lastIndexOf(t) charCodeAt(i) codePointAt(i) localeCompare(t) true বা false includes(t) startsWith(t) endsWith(t) s < t, s > t s === t একটা array split(sep) array-র উপর join(sep) দিলে আবার string কোনো method s-কে নিজে বদলায় না। প্রথম বাক্সের প্রতিটা string নতুন (lesson 01)।

তাহলে উত্তরের type-ই ঠিক করে পরের ধাপ। Number-কে তুলনা করা যায়, index হিসেবেও ব্যবহার করা যায়। Boolean বসে কোনো if-এ, আর array-কে জুড়ে আবার string বানানো যায়।

length, s[i], at() আর charAt(): একটা বাক্স পড়া

একটা code unit পড়া

s.length       কয়টা code unit
s[i]           index i-তে যে code unit, শেষের পরে গেলে undefined
s.at(i)        একই, আর negative i শেষ থেকে গোনে
s.charAt(i)    s[i]-এর মতোই, কিন্তু শেষের পরে গেলে ""
const name = "Kenji";
console.log(name.length);
console.log(name[0], name[4], name.at(-1), name.at(-2));
console.log(name[5], name.at(5));
console.log("[" + name.charAt(5) + "]");
5
K i i j
undefined undefined
[]

এগুলো lesson 01-এরই হাতিয়ার, পুরো তালিকার খাতিরে এখানে রাখা। at(-1) হলো শেষ code unit, আর at(-2) তার আগেরটা। তিনটার মধ্যে সবচেয়ে পুরনো charAt: শেষের পরে গেলে এটা দেয় খালি string, যেখানে s[i] আর at দেয় undefined।

Callখরচকারণ
s.length, s[i], s.at(i), s.charAt(i)O(1)length জমা রাখা থাকে, আর একটা code unit-এর জায়গা হলো শুরু যোগ i; একমাত্র ব্যতিক্রম দেখাবে lesson 05: লম্বা একটা += loop দিয়ে সদ্য বানানো string

যেটায় চোখ রাখবে: length গোনে code unit, reader চোখে যত character দেখে সেটা না (lesson 01-এর "মাহি 😀"-এর length 7)। তাই এই lesson-এর প্রতিটা index হলো code unit-এর index।

slice আর substring: একটা টুকরো কেটে নেওয়া

টুকরো কাটা

s.slice(start, end)       start থেকে end পর্যন্ত, end নিজে বাদ; negative গোনে শেষ থেকে
s.slice(start)            start থেকে string-এর শেষ পর্যন্ত
s.substring(start, end)   একই টুকরো, কিন্তু start বড় হলে দুইটা অদলবদল করে,
                          আর negative সংখ্যাকে 0 ধরে
  • দুইটাই নতুন একটা string ফেরত দেয়। end-এর code unit কখনো ঢোকে না, তাই টুকরোয় থাকে end - start-টা code unit।
const lang = "JavaScript";
console.log(lang.slice(4));
console.log(lang.slice(-6));
console.log(lang.slice(0, 4));
console.log(lang.slice(4, -2));
console.log("[" + lang.slice(4, 1) + "]");
console.log(lang.substring(4, 1));
console.log(lang.substring(-3));
console.log(lang.substr(4, 3));
console.log("num:42".slice(4));
Script
Script
Java
Scri
[]
ava
JavaScript
Scr
42

প্রথম তিন রকম call-এ দুইটা method একমত, বাকিগুলোতে না। বাক্সগুলোর উপর একটা একটা করে চালাও, আর দেখো কোন call কোন বাক্স বেছে নেয়।

"JavaScript" string-টা দশটা বাক্স, 0 থেকে 9 নম্বর। slice(4) আর slice(-6) দুইটাই নেয় 4 থেকে 9 নম্বর বাক্স, slice(0, 4) নেয় 0 থেকে 3, slice(4, 1) খালি, substring(4, 1) সংখ্যা দুইটা অদলবদল করে নেয় 1 থেকে 3, আর substring(-3) -3-কে 0 ধরে পুরো string-টাই নেয়।

index0123456789
code unitJavaScript
hex004A006100760061005300630072006900700074
Expressionফলকী হলো
s.slice(4)"Script"একটাই সংখ্যা: 4 নম্বর বাক্স থেকে string-এর শেষ পর্যন্ত।
s.slice(-6)"Script"Negative শুরু গোনা হয় শেষ থেকে: -6 মানে length - 6, অর্থাৎ 4। সেই একই ছয়টা বাক্স।
s.slice(0, 4)"Java"0 নম্বর বাক্স থেকে 4 পর্যন্ত, কিন্তু 4 নম্বরটা নিজে বাদ: মোট চারটা বাক্স।
s.slice(4, 1)""শেষটা শুরুর আগে পড়েছে, তাই slice কিছুই নেয় না, দেয় খালি string।
s.substring(4, 1)"ava"substring আগে 4 আর 1 অদলবদল করে নেয়, তাই পায় 1, 2 আর 3 নম্বর বাক্স।
s.substring(-3)"JavaScript"substring যেকোনো negative সংখ্যাকে 0 ধরে, তাই পুরো string-টাই নেয়।

তাহলে slice negative সংখ্যা পড়ে "শেষ থেকে গোনো" হিসেবে, আর শুরুর আগে পড়া end-কে পড়ে "কিছুই না" হিসেবে। substring negative-কে ধরে 0, আর বেশি বড় start-কে চুপচাপ অদলবদল করে নেয়। slice-এর নিয়ম আগে থেকে আন্দাজ করা সহজ, তাই এই track লেখে slice। শেষ লাইনটা রাখে Module 2-এর কথা: token.slice(4) হলো প্রথম চারটা code unit-এর পরের text। তাই "num:42" দেয় "42"।

substr-এর লাইনটা দেখায় তৃতীয় সেই method, যেটা পুরনো code-এ দেখা পাবে। এর দ্বিতীয় সংখ্যাটা length, শেষ না: substr(4, 3) index 4 থেকে তিনটা code unit নেয়। Standard এটাকে রেখেছে Annex B-তে, যেটা শুধু পুরনো web page চালু রাখার জন্য রাখা feature-এর পরিশিষ্ট। Annex B নিজেই বলে, নতুন code-এ এগুলো ব্যবহার কোরো না। এর বদলে লেখো slice(4, 7)।

Callখরচকারণ
s.slice(a, b), s.substring(a, b)বড়জোর O(k)টুকরোর k-টা code unit copy হয়; লম্বা টুকরো হলে V8 copy না করে পুরনো string-এর memory ভাগাভাগি করে নিতে পারে (lesson 05)

যেটায় চোখ রাখবে: end বাদ পড়ে। Bob চায় "Kenji"-এর প্রথম তিনটা অক্ষর, লেখে slice(0, 2), আর পায় "Ke"। a থেকে b পর্যন্ত টুকরোয় থাকে b - a-টা code unit, তাই প্রথম তিনটা হলো slice(0, 3)।

indexOf, lastIndexOf আর includes: খোঁজা

খোঁজা

s.indexOf(t)           s-এর ভিতরে t প্রথম যে index-এ শুরু হয়, না পেলে -1
s.indexOf(t, from)     একই, তবে খোঁজা শুরু হয় index from থেকে
s.lastIndexOf(t)       t শেষবার যে index-এ শুরু হয়, না পেলে -1
s.includes(t)          s-এর কোথাও t থাকলে true, না থাকলে false
const title = "JavaScript for Kids";
console.log(title.indexOf("Java"), title.indexOf("for"), title.indexOf("Python"));
console.log(title.indexOf("i"), title.lastIndexOf("i"), title.indexOf("i", 8));
console.log(title.includes("Kids"), title.includes("kids"));
console.log(title.indexOf("Java") !== -1, "Python Basics".includes("Java"));
0 11 -1
7 16 16
true false
true false

indexOf উত্তর দেয় "কোথায়": প্রথম মিলের index। কোনো মিল না থাকলে উত্তর -1, এমন একটা সংখ্যা যেটা কখনো index হতে পারে না। includes উত্তর দেয় "আছে কি না", একটা boolean দিয়ে। দুইটাই case-sensitive, মানে বড় হাতের আর ছোট হাতের অক্ষর আলাদা। তাই যে title-এ লেখা Kids, সেখানে "kids" পাওয়া যায় না।

এবার Bob-এর search box। নিচে তিনটা title ওর নিয়মে check করা, প্রতিটা রায়ের পাশে indexOf যে সংখ্যা দিয়েছে সেটা।

const word = "Java";
const a = "Java for Kids";
const b = "Learn Java";
const c = "Python Basics";
console.log(a.indexOf(word), a.indexOf(word) ? "found" : "not found");
console.log(b.indexOf(word), b.indexOf(word) ? "found" : "not found");
console.log(c.indexOf(word), c.indexOf(word) ? "found" : "not found");
0 not found
6 found
-1 found

প্রতিটা রায় নির্ভর করছে সংখ্যাটা truthy কি না, তার উপর (Module 2)। শুরুতেই মিললে আসে 0, আর 0 falsy, তাই প্রথম title "not found"। শব্দ না থাকলে আসে -1, আর -1 truthy, তাই Python Basics "found"। Bob-এর লাইন দুই দিক থেকেই ভুল। আসল প্রশ্নটাই করো: title.includes(word), অথবা title.indexOf(word) !== -1।

Callখরচকারণ
s.indexOf(t), s.lastIndexOf(t), s.includes(t)O(n + k)খোঁজাটা s-এর ভিতর দিয়ে একবার হাঁটে; সাদামাটা খোঁজা প্রতিটা জায়গায় আবার চেষ্টা করতে পারত, কিন্তু লম্বা text খুঁজতে V8 Boyer-Moore পদ্ধতি নেয়, যেটা লাফিয়ে সামনে এগোয়। এটা সাধারণ খরচ: কোনো engine-ই এই খরচের কথা দেয় না, আর ইচ্ছে করে বানানো কোনো search text-এ খরচ আরও বেশি হতে পারে

যেটায় চোখ রাখবে: indexOf-এর সংখ্যাটাকে কখনো হ্যাঁ-না উত্তর হিসেবে ব্যবহার কোরো না। "আছে কি না"-র জন্য includes, আর "কোথায়"-র জন্য রাখো indexOf।

startsWith আর endsWith: দুই মাথা check করা

দুই মাথা check করা

s.startsWith(t)         s যদি t দিয়ে শুরু হয়, true
s.startsWith(t, at)     t যদি index at-এ শুরু হয়, true
s.endsWith(t)           s যদি t দিয়ে শেষ হয়, true
const file = "report-2026.pdf";
console.log(file.startsWith("report"), file.endsWith(".pdf"));
console.log(file.endsWith(".PDF"), file.toLowerCase().endsWith(".pdf"));
console.log(file.startsWith("2026", 7), file.startsWith("2026"));
true true
false true
true false

এই দুইটা নাম দেখেই বোঝা যায় কী test করে, তাই পাঠককে কোনো index হিসাব করতে হয় না। দ্বিতীয় লাইনে আছে এদের রোজকার সঙ্গী: আগে case ছোট করো, তারপর check করো, তাহলে .PDF-ও পাস করবে। একটা method-এর ফলের উপর আরেকটা method call করাকে বলে chaining।

Callখরচকারণ
s.startsWith(t), s.endsWith(t)O(k)শুধু t-এর k-টা code unit একটা জানা জায়গায় মেলানো হয়; s-এর বাকিটা পড়াই হয় না

যেটায় চোখ রাখবে: file-এর নামের শেষটা ভিতরে কী আছে তার প্রমাণ না। endsWith দিয়ে নাম সাজাও, upload-কে বিশ্বাস করার জন্য না।

toUpperCase আর toLowerCase: case বদলানো

Case বদলানো

s.toUpperCase()     বড় হাতের অক্ষরে নতুন একটা string
s.toLowerCase()     ছোট হাতের অক্ষরে নতুন একটা string
const city = "Dhaka";
const loud = city.toUpperCase();
console.log(loud, city);
console.log("ZARA".toLowerCase(), "straße".toUpperCase(), "straße".toUpperCase().length);
console.log("Amara" === "AMARA", "Amara".toLowerCase() === "AMARA".toLowerCase());
DHAKA Dhaka
zara STRASSE 7
false true

city এখনো Dhaka-ই: method নতুন একটা string বানিয়েছে, পুরনোটায় হাত দেয়নি, lesson 01 যেমন কথা দিয়েছিল। জার্মান ß-এর আলাদা কোনো বড় হাতের রূপ নেই, তাই এটা হয়ে যায় SS, আর length 6 থেকে বেড়ে 7। শেষ লাইনটাই রোজকার ব্যবহার: দুই পাশ ছোট করো, তারপর তুলনা করো, তাহলে বড়-ছোট হাতের ঝামেলা আর থাকে না। বাংলা অক্ষরের কোনো case নেই, তাই এই method-গুলো বাংলায় কিছুই বদলায় না।

Callখরচকারণ
s.toUpperCase(), s.toLowerCase()O(n)প্রতিটা code unit পড়া হয়, আর মোটামুটি n code unit-এর নতুন একটা string লেখা হয়

যেটায় চোখ রাখবে: ফলটা রেখে দিতে হবে। আলাদা লাইনে শুধু name.toUpperCase(); লিখলে চোখে পড়ার মতো কিছুই হয় না ("যে ভুলগুলো সবাই করে"-র 1 নম্বর)।

trim, trimStart আর trimEnd: দুই মাথার space ছাঁটা

ছাঁটা

s.trim()         দুই মাথার whitespace বাদ দিয়ে নতুন একটা string
s.trimStart()    শুধু শুরুরটা
s.trimEnd()      শুধু শেষেরটা
  • Whitespace মানে space, tab আর line break, সাথে আরও কয়েকটা কম দেখা space character।
const typed = "   Zara  ";
console.log("[" + typed.trim() + "]");
console.log("[" + typed.trimStart() + "]");
console.log("[" + typed.trimEnd() + "]");
console.log("\tok\r\n".length, "\tok\r\n".trim().length);
console.log("[" + "Amara  writes".trim() + "]");
[Zara]
[Zara  ]
[   Zara]
5 2
[Amara  writes]

Bracket থাকায় space-গুলো চোখে পড়ে। চতুর্থ লাইন একটা tab (\t) আর Windows-এর line ending (\r\n) সরিয়ে দেয়: 5টা code unit থেকে থাকে 2টা। শেষ লাইন দেখায় trim কোনটায় কখনো হাত দেয় না: মাঝখানের space।

Callখরচকারণ
s.trim(), s.trimStart(), s.trimEnd()O(n)দুই মাথা থেকে ভিতরের দিকে পড়তে থাকে, যতক্ষণ না space নয় এমন কিছু পায়; শুধু space দিয়ে ভরা string পুরোটাই পড়তে হয়

যেটায় চোখ রাখবে: input Windows থেকে এলে lines variant দিয়ে পড়া লাইনের শেষে একটা \r থেকে যায়। trim() বা trimEnd() সেটা সরায়।

split আর join: ভেঙে টুকরো, আবার জোড়া

সবচেয়ে বেশি ব্যবহার করবে এই জোড়াটাই। split একটা string-কে কেটে বানায় একটা array, মানে value-র একটা ক্রমওয়ালা তালিকা (array শেখাবে Module 7)। join হলো array-র method, যেটা টুকরোগুলো জুড়ে আবার একটা string বানায়।

ভাঙা আর জোড়া

s.split(sep)          প্রতিটা sep-এর মাঝের টুকরোগুলোর একটা array
s.split(sep, limit)   শুধু প্রথম limit-টা টুকরো
s.split("")           প্রতিটা code unit-এ একটা করে টুকরো
parts.join(sep)       একটা string: টুকরোগুলো, মাঝে মাঝে sep
parts.join()          একই, মাঝে comma দিয়ে
const csv = "Alice,Bob,Zara";
console.log(csv.split(","));
console.log(csv.split(",", 2));
console.log("Zara".split(","));
console.log("abc".split(""));
console.log(csv.split(",").join(" and "));
console.log(["2026", "10", "07"].join("-"));
console.log(csv.split(",").join());
[ 'Alice', 'Bob', 'Zara' ]
[ 'Alice', 'Bob' ]
[ 'Zara' ]
[ 'a', 'b', 'c' ]
Alice and Bob and Zara
2026-10-07
Alice,Bob,Zara

Node array print করে square bracket-এর ভিতরে, প্রতিটা string single quote-এ। যে string-এ separator নেই, সেটাও একটা array দেয়, এক টুকরোর। split("") প্রতিটা code unit-এ একটা টুকরো দেয়, সাধারণ ইংরেজি অক্ষরে এতে সমস্যা নেই। কিন্তু emoji দুইটা code unit, তাই split("") ওটাকে মাঝখান দিয়ে কেটে ফেলে; নিরাপদ উপায়টা দেখাবে lesson 04।

এবার Module 1-এর starter-এর সেই লাইন, যেটা তুমি প্রতিটা program-এ paste করেছ: input.split(/\s+/).filter(Boolean)। কেন এভাবে লেখা, সেটা দেখো বাড়তি space-ওয়ালা ছোট একটা input-এ চালিয়ে।

const input = "  3\n10  20 30\n";
console.log(input.split(" "));
console.log(input.split(/\s+/));
console.log(input.split(/\s+/).filter(Boolean));
[ '', '', '3\n10', '', '20', '30\n' ]
[ '', '3', '10', '20', '30', '' ]
[ '3', '10', '20', '30' ]

টুকরো 1, split(" ") কেন না। একটা space-কে separator ধরলে দুই জায়গায় ঠকবে। পরপর দুইটা space থাকলে মাঝখানে একটা খালি টুকরো থেকে যায়। আর line break তো space না, তাই '3\n10' জোড়া লেগেই থাকে।

টুকরো 2, /\s+/। দুই slash-এর মাঝের separator-টা একটা regular expression, মানে নির্দিষ্ট কোনো text না, একটা pattern। \s মানে যেকোনো whitespace character, আর + মানে "পরপর এক বা একাধিক"। তাই space, tab আর line break-এর যেকোনো সারি মিলে একটাই কাট। Regular expression ঠিকমতো শেখাবে Module 16।

টুকরো 3, দুই মাথার খালি টুকরো। Input শুরু হয়েছে space দিয়ে, শেষ হয়েছে line break দিয়ে। একদম শুরুতে কাট পড়লে তার আগে একটা খালি টুকরো থাকে, আর একদম শেষে কাট পড়লে তার পরে একটা। দ্বিতীয় লাইনের দুই মাথার '' সেখান থেকেই।

টুকরো 4, filter(Boolean)। filter হলো array-র একটা method (Module 7), যেটা শুধু সেই টুকরোগুলো রাখে, যেগুলোকে একটা test হ্যাঁ বলে। এখানে test হলো Boolean, Module 2-এর converter, value হিসেবে পাঠানো (function এভাবে পাঠানো দেখাবে Module 5)। খালি string falsy, তাই প্রতিটা '' বাদ পড়ে। তাহলে starter-এর লাইনটার মানে: whitespace-এর প্রতিটা সারিতে কাটো, তারপর খালি টুকরোগুলো ফেলে দাও।

Callখরচকারণ
s.split(sep), parts.join(sep)O(n + k)split s-এর ভিতর দিয়ে একবার হাঁটে আর টুকরোগুলো copy করে; join প্রতিটা টুকরো একবার করে copy করে k code unit-এর ফলে

যেটায় চোখ রাখবে: split ফেরত দেয় array, string না। আবার string চাইলে শেষে join দাও।

replace আর replaceAll: text বদলানো

বদলানো

s.replace(a, b)      নতুন string, যেখানে শুধু প্রথম a-টা b হয়ে গেছে
s.replaceAll(a, b)   নতুন string, যেখানে প্রতিটা a b হয়ে গেছে
const msg = "red, red, red";
console.log(msg.replace("red", "blue"));
console.log(msg.replaceAll("red", "blue"));
console.log(msg);
const price = "1,299";
console.log(Number(price));
console.log(Number(price.replaceAll(",", "")));
console.log(Number("1,299,000".replaceAll(",", "")) + 1);
blue, red, red
blue, blue, blue
red, red, red
NaN
1299
1299001

প্রথম argument string হলে replace বদলায় শুধু প্রথম মিলটা। replaceAll এসেছে ES2021-এ, আর এটা সবগুলো বদলায়। msg নিজে কখনো বদলায়নি। দ্বিতীয় অর্ধেকটা রাখে "1,299" নিয়ে Module 2-এর কথা: প্রতিটা comma-কে কিছু না দিয়ে বদলাও, তারপর convert করো, দাম দাঁড়ায় 1299।

Callখরচকারণ
s.replace(a, b), s.replaceAll(a, b)O(n + k)indexOf-এর মতো s-এর ভিতরে একবার খোঁজা, তারপর k code unit-এর নতুন একটা string বানানো

যেটায় চোখ রাখবে: string দিয়ে replace মানে "প্রথমটা বদলাও"। "সবগুলো বদলাও" বোঝাতে চাইলে লেখো replaceAll ("যে ভুলগুলো সবাই করে"-র 2 নম্বর)।

padStart আর padEnd: column লাইনে আনা

Padding

s.padStart(w)          s-এর বাঁয়ে space বসায়, যতক্ষণ না s w code unit লম্বা হয়
s.padStart(w, fill)    একই, space-এর বদলে fill দিয়ে
s.padEnd(w)            space বসায় ডানে
console.log("[" + "7".padStart(3) + "]");
console.log("[" + "7".padEnd(3) + "]");
console.log("42".padStart(5, "0"), "5".padStart(4, "ab"));
console.log("Zara".padStart(2));
[  7]
[7  ]
00042 aba5
Zara

Fill একটার বেশি code unit হলে বারবার বসে, আর মাপমতো কেটে নেওয়া হয়। যে string আগে থেকেই w-এর সমান লম্বা, সেটা হুবহু ফেরত আসে, কখনো কাটা পড়ে না। এগুলো string-এর method, তাই number-কে আগে String(x) দিয়ে string বানিয়ে নাও। Module 1 এই কথাটাই দিয়েছিল, column মিলিয়ে একটা নামতা লেখার জন্য।

const n = 12;
const width = String(n * 10).length;
for (let i = 1; i <= 10; i++) {
  console.log(n + " x " + String(i).padStart(2) + " = " + String(n * i).padStart(width));
}
12 x  1 =  12
12 x  2 =  24
12 x  3 =  36
12 x  4 =  48
12 x  5 =  60
12 x  6 =  72
12 x  7 =  84
12 x  8 =  96
12 x  9 = 108
12 x 10 = 120

সবচেয়ে চওড়া গুণফল হলো n * 10, তাই তার length-ই column-এর চওড়া, এখানে 3। প্রতিটা i pad হয় 2-এ, প্রতিটা গুণফল 3-এ, তাই = চিহ্নগুলো এক column-এ দাঁড়ায়। for loop-টা Module 1-এর; ঠিকমতো শেখাবে Module 4।

Callখরচকারণ
s.padStart(w), s.padEnd(w)O(k)k code unit-এর নতুন একটা string লেখা হয় (k হলো w, অথবা s আগে থেকেই বেশি লম্বা হলে n)

যেটায় চোখ রাখবে: padding গোনে code unit। বাংলা শব্দ বা emoji লাইনে আসে তার length ধরে, screen-এ কতটা চওড়া দেখায় সেই হিসেবে না।

repeat: একই text বারবার

বারবার লেখা

s.repeat(count)    পরপর count বার s; count হতে হবে 0 বা তার বেশি
console.log("=".repeat(12));
console.log("ab".repeat(3), "[" + "ab".repeat(0) + "]");
console.log("-".repeat(2.9));
console.log("*".repeat(-1));
============
ababab []
--
RangeError: Invalid count value: -1

Count-এ ভগ্নাংশ থাকলে সেটা কেটে পূর্ণসংখ্যা করা হয়, তাই 2.9 গোনা হয় 2। Negative count-এর কোনো মানে নেই, তাই program থেমে যায় একটা RangeError দিয়ে, যেটা আসে কোনো সংখ্যা অনুমোদিত সীমার বাইরে গেলে। Playground-এর verdict আসে Runtime error, আর থামার আগে print হওয়া তিনটা লাইন থেকে যায়।

Callখরচকারণ
s.repeat(count)O(k)ফলে থাকে k-টা code unit, মানে n গুণ count, আর প্রতিটা একবার করে লেখা হয়

যেটায় চোখ রাখবে: input থেকে হিসাব করা count negative হয়ে যেতে পারে। repeat call করার আগে check করে নাও।

concat আর +: জোড়ার দুইটা নাম

দুইটা string জোড়া

s.concat(t, ...)    s, তারপর প্রতিটা argument, প্রত্যেকটা string বানিয়ে
s + t               একই কাজ, Module 2-এর operator দিয়ে লেখা
const first = "Ada";
console.log(first.concat(" ", "Lovelace"));
console.log(first + " " + "Lovelace");
console.log("Room ".concat(7), "Room " + 7);
console.log(first);
Ada Lovelace
Ada Lovelace
Room 7 Room 7
Ada

concat আর + একই কাজ করে, আর দুইটাই number-কে আগে text বানায়। প্রায় সবাই লেখে +, অথবা template literal (শেষ অংশে)। concat এখানে আছে, যাতে অন্য কারো code-এ দেখলে চিনতে পারো।

Callখরচকারণ
s.concat(t), s + tO(1) থেকে O(k)ছোট ফল copy হয়, k code unit; লম্বা হলে V8 copy না করে string দুইটাকে জুড়ে রাখে (একটা rope, lesson 05)

যেটায় চোখ রাখবে: + চলে Module 2-এর নিয়মে, তাই "Room " + 7 + 1 হয় "Room 71"। অঙ্কের অংশটা parentheses-এ ঘিরে দাও।

charCodeAt, codePointAt আর String.fromCharCode: character মানে সংখ্যা

প্রতিটা code unit আসলে একটা সংখ্যা। Module 2 বলেছিল "1" হলো 49 আর "9" হলো 57, আর table-টা দেখানোর কথা দিয়েছিল। এই তিনটা call একটা character আর তার সংখ্যার মধ্যে আসা-যাওয়া করে।

Character-এর code

s.charCodeAt(i)               index i-এর code unit-এর সংখ্যা, শেষের পরে গেলে NaN
s.codePointAt(i)              i থেকে শুরু হওয়া পুরো character-এর সংখ্যা
String.fromCharCode(c, ...)   c, ... code unit দিয়ে বানানো নতুন একটা string
console.log(" ".charCodeAt(0), "0".charCodeAt(0), "A".charCodeAt(0), "a".charCodeAt(0));
console.log("Zara".charCodeAt(1), "Zara".charCodeAt(9));
console.log(String.fromCharCode(72, 105, 33));
console.log("😀".length, "😀".charCodeAt(0), "😀".codePointAt(0));
console.log(String.fromCharCode("c".charCodeAt(0) - 32), "7".charCodeAt(0) - 48);
32 48 65 97
97 NaN
Hi!
2 55357 128512
C 7

চতুর্থ লাইনে আবার lesson 01-এর সেই সাবধানবাণী। Emoji-টা দুইটা code unit, তাই charCodeAt(0) দেখে শুধু প্রথম অর্ধেক, 55357। codePointAt(0) দুই অর্ধেক একসাথে পড়ে, আর দেয় character-টার আসল সংখ্যা, 128512। দুই অর্ধেক কীভাবে মেলে, সেটা বুঝিয়ে বলবে lesson 05।

শেষ লাইনে দুইটা কৌশল আছে, নিচের table দেখলে যেগুলো পরিষ্কার হয়ে যাবে। ছোট হাতের অক্ষর থেকে 32 বাদ দিলে তার বড় হাতের রূপ, আর কোনো অঙ্কের code থেকে 48 বাদ দিলে তার মান। চারটা সংখ্যা মুখস্থ রাখার মতো, কারণ বাকি table এদের থেকেই বের করা যায়:

  • ' ' (space) হলো 32।
  • '0' হলো 48, তাই '9' হলো 57।
  • 'A' হলো 65, তাই 'Z' হলো 90।
  • 'a' হলো 97, তাই 'z' হলো 122, আর প্রতিটা ছোট হাতের অক্ষর তার বড় হাতের রূপ যোগ 32।

এই হলো ASCII-এর 128টা code, 1963 সালের আমেরিকান code table, যেটাকে Unicode তার প্রথম 128টা জায়গায় হুবহু রেখে দিয়েছে। একেকটা column উপর থেকে নিচে পড়ো: প্রতিটায় পরপর 16টা code। JavaScript-এ 0 থেকে 127 পর্যন্ত code unit ঠিক এগুলোই। Learn C track করে থাকলে চিনবে, এটা ওই track-এর Module 1-এর lesson "The Complete ASCII Table, All 128 Rows"-এর সেই একই table।

0 থেকে 1516 থেকে 3132 থেকে 4748 থেকে 6364 থেকে 7980 থেকে 9596 থেকে 111112 থেকে 127
0 NUL16 DLE32 SP48 064 @80 P96 `112 p
1 SOH17 DC133 !49 165 A81 Q97 a113 q
2 STX18 DC234 "50 266 B82 R98 b114 r
3 ETX19 DC335 #51 367 C83 S99 c115 s
4 EOT20 DC436 $52 468 D84 T100 d116 t
5 ENQ21 NAK37 %53 569 E85 U101 e117 u
6 ACK22 SYN38 &54 670 F86 V102 f118 v
7 BEL23 ETB39 '55 771 G87 W103 g119 w
8 BS24 CAN40 (56 872 H88 X104 h120 x
9 TAB25 EM41 )57 973 I89 Y105 i121 y
10 LF26 SUB42 *58 :74 J90 Z106 j122 z
11 VT27 ESC43 +59 ;75 K91 [107 k123 {
12 FF28 FS44 ,60 <76 L92 \108 l124 |
13 CR29 GS45 -61 =77 M93 ]109 m125 }
14 SO30 RS46 .62 >78 N94 ^110 n126 ~
15 SI31 US47 /63 ?79 O95 _111 o127 DEL

0 থেকে 31 আর 127 হলো control character: printer বা terminal-এর জন্য সংকেত, অক্ষর না। Table-এ এদের ছোট নামগুলো দেওয়া আছে। SP মানে space। এদের ছয়টা এখনো কাজে লাগে:

  • NUL (0) C-তে একটা string-এর শেষ বোঝায়; JavaScript-এ এটা সাধারণ একটা code unit, লেখা হয় "\0"।
  • TAB (9) হলো tab, "\t"।
  • LF (10) হলো line break, "\n", lines variant-এর separator।
  • CR (13) হলো "\r", Windows যেটা প্রতিটা LF-এর আগে বসায়।
  • ESC (27) দিয়ে শুরু হয় সেই code-গুলো, যেগুলো terminal-এ লেখায় রং দেয়।
  • DEL (127) ছিল paper tape-এর "delete": সবগুলো ফুটো করে দিলে ভুল অক্ষরটা মুছে যেত।
Callখরচকারণ
s.charCodeAt(i), s.codePointAt(i), String.fromCharCode(c)O(1), আর k code unit বানাতে O(k)জানা একটা index থেকে একটা সংখ্যা পড়া; string বানাতে প্রতিটা code unit একবার করে লেখা

যেটায় চোখ রাখবে: যোগ 32 আর বিয়োগ 48-এর কৌশল খাটে শুধু ASCII অক্ষর আর অঙ্কে। é বা বাংলা অক্ষরের বেলায় এর কোনো মানে নেই।

String-এর তুলনা: <, > আর localeCompare

তুলনা

s < t                 code unit-এর ক্রমে s আগে এলে true
s.localeCompare(t)    negative একটা সংখ্যা, 0, অথবা positive একটা সংখ্যা, মানুষ যে ক্রম আশা করে সেই হিসেবে
console.log("Z" < "a", "10" < "9", "apple" < "apricot");
console.log("ab" < "abc", "Zara" > "Amara", "Amara" === "amara");
console.log("a".localeCompare("b"), "b".localeCompare("a"), "a".localeCompare("a"));
console.log(["banana", "apple", "Cherry", "éclair"].sort());
console.log(["banana", "apple", "Cherry", "éclair"].sort((x, y) => x.localeCompare(y)));
console.log(["banana", "apple", "Cherry", "éclair"].sort(new Intl.Collator("en").compare));
true true true
true true false
-1 1 0
[ 'Cherry', 'apple', 'banana', 'éclair' ]
[ 'apple', 'banana', 'Cherry', 'éclair' ]
[ 'apple', 'banana', 'Cherry', 'éclair' ]

দুইটা string-এর মধ্যে < বাঁ দিক থেকে code ধরে ধরে তুলনা করে, আর প্রথম যেখানে অমিল, সেটাই রায় দেয়। Module 2-এর teaser-এর উত্তর এটাই। "Z" হলো 90 আর "a" হলো 97, তাই প্রতিটা বড় হাতের অক্ষর যেকোনো ছোট হাতের অক্ষরের আগে আসে। "10" < "9" true, কারণ "1" (49) হারিয়ে দেয় "9"-কে (57), বাকিটা আর পড়াই হয় না। যে string আগে ফুরিয়ে যায়, সেটাই ছোট।

মানুষ আশা করে apple আসবে Cherry-র আগে, বড় হাত ছোট হাত যা-ই হোক। localeCompare কোনো ভাষার এই সাজানোর নিয়ম মেনে চলে, যার নাম collation। শেষের আগের লাইনটা এটাকে array-র sort-এ (Module 7) পাঠায়, একটা arrow function দিয়ে (Module 5)। Intl.Collator একই তুলনাটা একবার বানিয়ে রাখে বারবার ব্যবহারের জন্য; কখন কোনটা খাটে, সেটা ঠিক করবে lesson 04।

Callখরচকারণ
s < t, s.localeCompare(t)O(n), n হলো ছোটটার length< থামে প্রথম অমিলে; localeCompare প্রতিটা character-এ বেশি কাজ করে, ভাষার নিয়ম মানতে গিয়ে

যেটায় চোখ রাখবে: অঙ্ক দিয়ে লেখা দুইটা string তুলনা হয় text হিসেবে। সংখ্যা হিসেবে তুলনা করতে চাইলে আগে Number() দিয়ে convert করো।

Template literal, পুরোটা

Lesson 01-এ দেখেছ backtick-এর ভিতরে লেখা string। ওই রূপটার নাম template literal, আর এর ভিতরে value আর line break দুইটাই রাখা যায়।

Template literal

`text ${expression} more text`    expression-এর value, text বানিয়ে, ঠিক ওই জায়গায় বসে
`line one
line two`                         আসল একটা line break, যেমন টাইপ করেছ তেমনই থাকে
\`   \${                           একটা backtick, অথবা dollar আর brace, সাধারণ text হিসেবে
  • ${ }-এর ভিতরে যেকোনো expression বসে: একটা নাম, একটা যোগফল, একটা method call, একটা ternary, এমনকি আরেকটা template literal।
const name = "Zara";
const books = 3;
const price = 450;
console.log(`${name} bought ${books} books for ${books * price} taka.`);
console.log(`Order type: ${books > 2 ? "bulk" : "single"}`);
const card = `Name:  ${name.toUpperCase()}
Books: ${books}`;
console.log(card);
console.log(`Paid: ${`${books * price}`.padStart(6, ".")} taka`);
console.log(`A backtick: \`, and a dollar sign: \${books}`);
Zara bought 3 books for 1350 taka.
Order type: bulk
Name:  ZARA
Books: 3
Paid: ..1350 taka
A backtick: `, and a dollar sign: ${books}

প্রথম লাইনে তিনটা value, অথচ একটাও + নেই। দ্বিতীয় লাইনে ভিতরে বসেছে একটা ternary। card source-এ দুই লাইন জুড়ে লেখা, print-ও হয় দুই লাইনে। চতুর্থ লাইনে ${ }-এর ভিতরে আরেকটা template, যেটা pad করার আগে মোট দামটাকে text বানায়। শেষ লাইন দেখায় দুইটা escape।

Backtick-এর ঠিক আগে কোনো function-এর নাম লিখলে, যেমন String.raw, সেটা হয় একটা tagged template। Function-টা টুকরোগুলো হাতে পায়, আর ফল কী হবে সেটা ঠিক করে। Database query-র মতো কাজে library-গুলো tag ব্যবহার করে। এই track এদের নাম বলে রাখে, শেখায় না।

Callখরচকারণ
`...${x}...`O(k)প্রতিটা value text হয়, আর প্রতিটা টুকরো k code unit-এর ফলে একবার করে লেখা হয়

যেটায় চোখ রাখবে: template literal তোমার টাইপ করা প্রতিটা space আর line break রেখে দেয়। card-এর দ্বিতীয় লাইন indent করলে indent-টাও print হবে।

Example 1: file-এর extension

Amara-র photo app-কে প্রতিটা file-এর ধরন জানতে হয়, নামের শেষ dot-এর পরের লেখা থেকে।

const file = "holiday.photo.JPG";
const dot = file.lastIndexOf(".");
const ext = file.slice(dot + 1).toLowerCase();
console.log(dot, ext);
console.log(ext === "jpg" ? "an image" : "something else");
console.log("README".lastIndexOf("."));
13 jpg
an image
-1

lastIndexOf প্রথম dot-টা পেরিয়ে খুঁজে পায় index 13-এর dot-টা। slice(dot + 1) শুরু করে তার ঠিক পরের ঘর থেকে, আর toLowerCase JPG আর jpg-কে এক করে দেয়। শেষ লাইনটা Zara-র প্রথম test: dot নেই এমন নাম দেয় -1, আর তখন slice(0) পুরো নামটাই ফেরত দিত। অনুশীলন 3-এ program-টাকে সেটা সামলাতে শেখাবে।

Run in Compiler
Example 2: sign-up তালিকা পরিষ্কার করা

Maria-র newsletter form email address জমা করে মানুষ যেভাবে টাইপ করেছে সেভাবেই: দুই মাথায় space, বড় হাতের অক্ষর, আর মাঝে মাঝে @-ই নেই। ওর program প্রতি লাইনে একটা address পড়ে, তাই ব্যবহার করে starter-এর lines variant।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

const n = nextInt();
for (let i = 0; i < n; i++) {
  const email = next().trim().toLowerCase();
  const verdict = email.includes("@") ? "ok" : "missing @";
  out.push(`${email.padEnd(18)} ${verdict}`);
}

console.log(out.join("\n"));
zara@example.com   ok
bob.example.com    missing @
alice@mail.com     ok

ওই output-টা input 3 আর তিনটা address-এর জন্য, যার প্রথমটার দুই মাথায় space। প্রতিটা address একটা chain-এর ভিতর দিয়ে যায়: আগে trim, তারপর toLowerCase। হ্যাঁ-না প্রশ্নটা করে includes, আর padEnd-ওয়ালা একটা template literal রায়গুলো এক column-এ সাজায়।

Run in Compiler
Example 3: Bob-এর search box, এবার ঠিকঠাক

Bob ওর search নতুন করে লেখে। Program পড়ে শব্দটা, তারপর n-টা title, প্রতি লাইনে একটা। বড়-ছোট হাতের তফাত এটা মানে না, আর শব্দটা কোথায় শুরু হয়েছে সেটাও বলে।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

const word = next().trim().toLowerCase();
const n = nextInt();
for (let i = 0; i < n; i++) {
  const title = next();
  const where = title.toLowerCase().indexOf(word);
  const verdict = where === -1 ? "not found" : `found at ${where}`;
  out.push(`${title.padEnd(22)} ${verdict}`);
}

console.log(out.join("\n"));
Java for Kids          found at 0
Learn JavaScript       found at 6
Python Basics          not found
The JAVA Cookbook      found at 4

ওই output-টা শব্দ java, তারপর 4 আর চারটা title-এর জন্য। প্রথম title পাওয়া গেছে 0-তে, ঠিক যে জায়গায় Bob-এর প্রথম version ভেঙেছিল, কারণ test এখন where === -1। দুই পাশ ছোট হাতের করায় JAVA-ও ধরা পড়ে। Index-টা ছোট হাতের করা title-এর, তবে এই ইংরেজি title-গুলোতে দুইটার length একই।

Run in Compiler

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

  • Node-এর path module। path.extname("index.coffee.md") ফেরত দেয় ".md", file-এর নামের শেষ dot থেকে বাকিটা, আর path.extname(".bashrc") দেয় ""। Example 1-এর কাজটাই, Node-এর ভিতরে বানানো।
  • URL API। Browser আর Node দুই জায়গাতেই new URL("https://example.com/books/strings/02").pathname হলো "/books/strings/02"। কোন page দেখাবে ঠিক করতে একটা router এটাকে "/" ধরে ভাঙে, পায় ['', 'books', 'strings', '02']।
  • nginx-এর access log। nginx-এর default combined log format প্রতিটা request-এর জন্য এক লাইন লেখে, field-গুলো space দিয়ে আলাদা, সবার আগে client-এর address। ছোট একটা report script প্রতিটা লাইন space ধরে ভাঙে। মাঝখানের quote করা request-এর ভিতরেও space আছে: lesson 04-এর quote করা comma-র সমস্যাটাই, অন্য চেহারায়।
  • Screen-এ দাম। new Intl.NumberFormat("en-IN").format(1299000) দেয় "12,99,000", ভারতীয় ধাঁচে comma বসিয়ে। এই lesson-এর replaceAll(",", "")-এর ঠিক উল্টো কাজ: comma মানুষের জন্য, আর হিসাব করার আগে program সেগুলো সরিয়ে নেয়।

যে ভুলগুলো সবাই করে

১. Method call করে তার ফল ফেলে দেওয়া।

const name = "zara";
name.toUpperCase();
console.log(name);
const loud = name.toUpperCase();
console.log(loud);
zara
ZARA

কোনো error নেই, আর কিছু বদলায়ওনি। String method নতুন একটা string ফেরত দেয়, আর লাইন 2 সেটা মাটিতে ফেলে দিয়েছে। ফলটা একটা নামে রাখো, অথবা সাথে সাথে ব্যবহার করো। তুমি লাইন 2 লিখবে, কারণ কিছু method সত্যিই নিজের value বদলায়, যেমন array-র sort (Module 7)।

২. সব comma সরাতে replace ব্যবহার করা।

const price = "1,299,000";
console.log(price.replace(",", ""));
console.log(Number(price.replace(",", "")));
console.log(Number(price.replaceAll(",", "")));
1299,000
NaN
1299000

প্রথম comma গেছে, দ্বিতীয়টা রয়ে গেছে, তাই convert করলে আসে NaN। replaceAll ব্যবহার করো। তুমি replace লিখবে, কারণ test-এর দাম "1,299"-এ comma একটাই, তাই ওটা পাস করে যায়।

৩. substring-এ negative index।

const name = "Kenji";
console.log(name.substring(-3));
console.log(name.slice(-3));
Kenji
nji

Bob চেয়েছিল শেষ তিনটা অক্ষর, পেয়েছে পুরো নামটা, কোনো message ছাড়াই। substring -3-কে পড়ে 0 হিসেবে। লেখো slice(-3)। দুইটা গুলিয়ে ফেলবে, কারণ সাধারণ ইংরেজিতে দুইটা নামের মানে একই।

৪. যে লাইন নেই, তার উপর method call করা।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

const n = nextInt();
for (let i = 0; i < n; i++) {
  out.push(next().trim());
}

console.log(out.join("\n"));
TypeError: Cannot read properties of undefined (reading 'trim')

Input বলছে 4টা নাম আসবে, আছে 2টা। শেষের line break থেকে একটা খালি লাইন আসে, আর চতুর্থ next() শেষ পেরিয়ে গিয়ে দেয় undefined। undefined-এর উপর method call করলে program থেমে যায়, verdict আসে Runtime error, আর কিছুই print হয় না, কারণ print হয় সবার শেষে। Test-এর লাইন আর n মিলছে কি না দেখে নাও। হাতে টাইপ করা test input যখন প্রথম লাইনের কথার চেয়ে ছোট হবে, তখনই এটার দেখা পাবে।

মাথা খাটাও

দুইটা method-কে বিশ্বাস করার আগে Zara একটা শব্দের উপর চালিয়ে দেখে।

console.log("banana".replace("a", "o"));
console.log("banana".split("a"));

দুই লাইনের output হুবহু আন্দাজ করো, bracket আর quote সহ। দ্বিতীয় লাইনে কয়টা টুকরো print হবে, আর শেষেরটা খালি কেন?

String দিয়ে replace বদলায় শুধু প্রথম মিলটা। split-এর জন্য banana-র প্রতিটা a-তে একটা কাট দাগাও। তারপর কাটগুলোর মাঝে, আর শেষ কাটের পরে, কী আছে লিখে ফেলো।

অনুশীলন ১সহজ

Amara-র blog editor প্রতিটা paragraph-এর নিচে শব্দ গুনে দেখায়। ও যে লাইনগুলো পাঠায়, তার প্রতিটায় কয়টা শব্দ, গুনে বের করো।

Input. প্রথম লাইনে n, তারপর n-টা লাইন text। শব্দ মানে space নয় এমন character-এর সবচেয়ে লম্বা একটানা সারি। কোনো লাইন খালি হতে পারে, শুরুতে বা শেষে space থাকতে পারে, আর পরপর কয়েকটা space-ও থাকতে পারে।

Output. n-টা লাইন: text-এর প্রতিটা লাইনে কয়টা শব্দ, input-এর ক্রমে।

Constraints. 1 <= n <= 20000। Text-এর প্রতিটা লাইনে 0 থেকে 300টা printable ASCII character (code 32 থেকে 126), আর শেষ লাইনটা কখনো খালি না। প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

Sample. এই input দিলে চার লাইনে আসে 4, 0, 1 আর 4। Text-এর প্রথম লাইনে writes আর every-র মাঝে দুইটা space, দ্বিতীয়টা খালি, আর তৃতীয়টা শুরু হয়েছে দুইটা space দিয়ে।

4
Amara writes  every day

  hello
one two three four

লাইন খালি হতে পারে, তাই starter-টা lines variant। এর দ্বিতীয় লাইন const lines = input.split("\n");, তাই next() পুরো একটা লাইন ফেরত দেয়।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

// your code: read with next() and nextInt(), push every line of output to out
// Read n, then n lines. Per line: the number of words.

console.log(out.join("\n"));

word-count নামে grade হয়। Submit করার আগে একটা খালি লাইন, শুধু space-ওয়ালা একটা লাইন, আর দুই পাশে space-ওয়ালা একটা শব্দ দিয়ে চালিয়ে দেখো।

Run in Compiler
অনুশীলন ২সহজ

David একটা coding club-এর নামের badge print করে, আর ছোট badge-এ শুধু নামের আদ্যক্ষর ধরে। প্রতিটা পুরো নামকে তার আদ্যক্ষরে বদলাও।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় একটা পুরো নাম। প্রতিটা নাম ASCII অক্ষরের 1 থেকে 6টা শব্দ, মাঝে একটা করে space, কোনো মাথায় space নেই।

Output. প্রতিটা নামের জন্য এক লাইন: প্রতিটা শব্দের প্রথম অক্ষর বড় হাতে, প্রত্যেকটার পরে একটা dot, মাঝে আর কিছু না।

Constraints. 1 <= n <= 20000। প্রতিটা শব্দে 1 থেকে 20টা অক্ষর, বড় আর ছোট হাতের যেকোনো মিশেলে। Input-এর শেষ লাইন কখনো খালি না, আর প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

Sample. Input 3, তারপর ada lovelace, Grace Brewster Murray Hopper আর zara দিলে তিন লাইনে আসে A.L., G.B.M.H. আর Z.।

নামের ভিতরে space আছে, তাই এই starter-ও lines variant।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

// your code: read with next() and nextInt(), push every line of output to out
// Read n, then n names. Per name: the initials, each a capital and a dot.

console.log(out.join("\n"));

initials নামে grade হয়। Submit করার আগে এক শব্দের একটা নাম, আর পুরোটা ছোট হাতে লেখা একটা নাম দিয়ে চালিয়ে দেখো।

Run in Compiler
অনুশীলন ৩মাঝারি

Example 1-এর photo app-টাকে যে নামগুলো ভেঙে দিয়েছিল, Zara এবার সেগুলোই খাওয়ায়। Program-টা যেন প্রতিটা extension print করে, আর নামের আসল কোনো extension না থাকলে none।

Input. n, তারপর n-টা file-এর নাম। কোনো নামে space নেই।

Output. প্রতিটা নামের জন্য এক লাইন: শেষ dot-এর পরের লেখাটা, ছোট হাতের অক্ষরে। নামে কোনো dot না থাকলে, শেষ dot-টাই নামের প্রথম character হলে, অথবা শেষ dot-এর পরে কিছু না থাকলে print করো none।

Constraints. 1 <= n <= 1000। প্রতিটা নামে 1 থেকে 100টা character।

Sample. Input 5 আর photo.JPG archive.tar.gz README .bashrc notes. দিলে পাঁচ লাইনে আসে jpg, gz, none, none আর none।

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"));

আলাদা করে grade হয় না। Sample-এর প্রতিটা none আলাদা একেকটা ঘটনা। Test লেখার আগে প্রতিটার জন্য lastIndexOf(".") কত আসে, বের করে নাও।

Run in Compiler
অনুশীলন ৪কঠিন

Kenji গোনে একটা text-এ একটা pattern কতবার আছে, আর Maria জিজ্ঞেস করে "কিন্তু কোন গোনাটা?"। aaaa-তে aa শুরু হয় 0, 1 আর 2-তে, তাই মিলগুলো একটার ঘাড়ে আরেকটা চড়তে পারলে আছে 3 বার। কোনো অক্ষর ভাগাভাগি না করে পাশাপাশি বসে মাত্র 2টা মিল। দুইটা গোনাই print করো।

Input. লাইন 1 হলো text, যার ভিতরে space থাকতে পারে। লাইন 2 হলো n। তারপর n-টা pattern, প্রতি লাইনে একটা, কোনোটাই খালি না।

Output. প্রতিটা pattern-এর জন্য এক লাইন: overlap করা মিলের সংখ্যা, একটা space, আর overlap না করা মিলের সংখ্যা, বাঁ দিক থেকে গুনে।

Constraints. Text-এ 1 থেকে 1000টা character। 1 <= n <= 100। প্রতিটা pattern-এ 1 থেকে 20টা character।

Sample. Text aaaa banana, তারপর 3, আর pattern aa, ana আর x। ওই input দিলে তিন লাইনে আসে 3 2, 2 1 আর 0 0।

const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[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"));

আলাদা করে grade হয় না। indexOf(t, from) একটা জায়গার পরের মিলটা খুঁজে দেয়। দুইটা গোনার তফাত শুধু এক জায়গায়: পরের খোঁজা কোথা থেকে শুরু হবে। split এই দুইয়ের কোনটা গোনে?

Run in Compiler

সচরাচর যে প্রশ্নগুলো আসে

  • এগুলো সব কি মুখস্থ করতে হবে?

    না। পরিবারগুলো আর কোনটা কী ফেরত দেয়, সেটা জানো, method map-এ যেমন আছে। Argument-এর ঠিক ক্রমটা একবার খুঁজলেই পাবে, আর editor টাইপ করার সময়ই দেখিয়ে দেয়। রোজ যেগুলো টাইপ করবে: slice, split, join, trim, includes আর template literal।

  • এত কিছুর দুইটা করে কেন: slice আর substring, s[i] আর charAt?

    JavaScript প্রায় কখনো কোনো method সরায় না, কারণ পুরনো web page এখনো সেগুলো call করে। তাই প্রতিটা নতুন, পরিষ্কার method বসে পুরনোটার পাশে। দুইটা একই কাজ করলে এই track সেটাই লেখে, যার নিয়ম সহজ। মানে slice, s[i] বা at, আর + বা template literal।

  • indexOf আর includes-এর মধ্যে কোনটা দ্রুত?

    দুইটা একই খোঁজা করে, তাই খরচও এক, O(n + k)। কোন প্রশ্ন করছ, সেটা দেখে বেছে নাও: "আছে কি না"-র জন্য includes, "কোথায়"-র জন্য indexOf। আরও উদাহরণ দিয়ে এই বাছাইটা করবে lesson 04।

  • split("") কেন "abc"-তে চলে, কিন্তু emoji-তে না?

    split("") কাটে code unit-এর মাঝে মাঝে, আর emoji হলো দুইটা code unit, তাই ওটা মাঝখান দিয়ে কাটা পড়ে। ইংরেজি অক্ষর একেকটা একটা code unit, তাই কিছু ভাঙে না। Lesson 04 দেখাবে [...s], যেটা কাটে পুরো character ধরে।

মূল কথা

  • কোনো method string বদলায় না; একটা code unit পড়ার খরচ O(1), আর ভিতর দিয়ে হাঁটার খরচ O(n) বা O(n + k)।
  • slice(start, end) end বাদ দেয় আর negative গোনে শেষ থেকে; substring অদলবদল করে আর negative-কে 0 ধরে; substr পুরনো জিনিস (Annex B)।
  • indexOf দেয় একটা জায়গা অথবা -1, তাই if (s.indexOf(x)) দুই দিক থেকেই ভুল: হ্যাঁ-না-র জন্য includes।
  • split string কেটে array বানায়, আর join আবার জোড়ে; replace বদলায় প্রথম মিল, replaceAll প্রতিটা মিল।
  • charCodeAt আর String.fromCharCode character থেকে code আর code থেকে character বানায় (anchor 32, 48, 65, 97); < তুলনা করে code, localeCompare মানুষের ক্রম।
  • Template literal ${ } দিয়ে যেকোনো expression-এর value text-এ বসায়, আর line break রেখে দেয়।
  • আরও গভীরে: Under the Hood, immutability, rope আর UTF-16 (Pro)।

এরপর lesson 03 এই method-গুলো দিয়ে ছয়টা পুরো program লিখবে, শব্দ গোনা থেকে দোকানের রসিদ পর্যন্ত।

lesson ২ শেষ

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

পরেরটা: পুরো program: কাজের মাঝে string

প্রতিটা string method, একটা একটা করে | Learn JavaScript | Progsity