Learn JavaScript

lesson ৩ / ৯ · string আর template literal

Module ৩ · string আর template literal

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

Freeপড়া

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

  • Judge-এর starter-এর উপর ছয়টা পুরো program লিখতে পারবে, যেগুলো text ভেঙে আবার নতুন করে জোড়া লাগায়: word counter থেকে শুরু করে print করা রসিদ পর্যন্ত।
  • বুঝিয়ে বলতে পারবে quote-এর ভেতরের comma পেলে split(",") কেন ভুল করে, আর word counter-এ কেন filter(Boolean) লাগে, palindrome check-এ কেন join("") লাগে।
  • Input যখন space-ওয়ালা text, তখন starter-এর lines variant বেছে নিতে পারবে, আর template literal, padEnd আর padStart দিয়ে output-এর কলাম সোজা করে সাজাতে পারবে।

Amara-র একটা ছোট বইয়ের blog আছে, সাথে একটা দোকানও। প্রতিটা post-এর title থেকে একটা web address বানাতে হয়, editor-এ word count দেখাতে হয়, আর প্রতিটা order-এ একটা রসিদ print হয়। প্রতিটা feature-এর নিচে বসে আছে ছোট একটা program, যেটা একটা string ভেঙে নতুন একটা string বানায়। এই lesson-এ আমরা এরকম ছয়টা লিখব, প্রতিটা মোটামুটি তিরিশ লাইনের, word counter থেকে রসিদ পর্যন্ত।

Starter, তার lines variant-এ

এখানকার প্রতিটা program input পড়ে Module 1-এর fixed starter দিয়ে। বেশিরভাগই পড়ে এমন text যার ভিতরে space আছে, যেমন একটা title বা একটা বাক্য। Token starter এই text-কে শব্দে শব্দে কেটে ফেলত, space-গুলো হারিয়ে যেত। তাই এই program-গুলো ব্যবহার করে lines variant, যেখানে ঠিক দুইটা লাইন বদলায়।

Lines variant, আর এই lesson-এর মূল টুকরোগুলো

const lines = input.split("\n");
const next = () => lines[at++];

text.split(" ").filter(Boolean)
text.split("").reverse().join("")
text.charCodeAt(i)        String.fromCharCode(code)
`${value}`.padStart(width)   name.padEnd(width)
  • প্রথম দুইটা লাইন token-এর split আর token-এর reader-এর জায়গা নেয়, তাই next() পুরো একটা লাইন ফেরত দেয়, খালি লাইন হলেও।
  • split(" ").filter(Boolean) space-এ কাটে, আর খালি টুকরোগুলো ফেলে দেয়।
  • split(""), reverse() আর join("") text-কে character-এ কাটে, উল্টে দেয়, তারপর আবার জোড়া লাগায়।
  • charCodeAt(i) একটা character-এর code দেয়; String.fromCharCode(code) code থেকে আবার character-এ ফেরে।
  • padStart আর padEnd বাঁয়ে বা ডানে space বসিয়ে text-কে width character লম্বা করে।

এখন থেকে output-এর প্রতিটা লাইন একটা template literal, lesson 02-এ যেমন দেখেছ, কোনো +-এর লম্বা শিকল ছাড়া।

শব্দ গোনা

Editor-এর হিসাবে শব্দ মানে পাশাপাশি কয়েকটা character, যার ভিতরে কোনো space নেই। প্রতিটা space-এ কাটলে প্রায় কাজ হয়ে যায়, কিন্তু পরপর দুইটা space মাঝখানে একটা খালি টুকরো রেখে যায়, খালি লাইনও তাই করে। filter(Boolean) সেই টুকরোগুলো ফেলে দেয়, কারণ "" falsy।

Example 1: editor-এর word count

নতুন যা: lines variant, আর পুরো একটা লাইনের উপর split(" ").filter(Boolean)। Program-টা একটা draft-এর n-টা লাইন পড়ে, তারপর মোট কয়টা শব্দ আর সবচেয়ে লম্বা শব্দ কোনটা, সেটা জানায়। ভিতরের loop-টা হলো for-of loop, যেটা Module 4 শেখাবে: array-র প্রতিটা শব্দে সে একবার করে যায়।

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();
let total = 0;
let longest = "";
for (let i = 0; i < n; i++) {
  const words = next().split(" ").filter(Boolean);
  total += words.length;
  for (const word of words) {
    if (word.length > longest.length) {
      longest = word;
    }
  }
}
out.push(`lines: ${n}`);
out.push(`words: ${total}`);
out.push(`longest: ${longest} (${longest.length} characters)`);

console.log(out.join("\n"));
lines: 4
words: 8
longest: Template (8 characters)

এই output-টা input 4 আর তারপরের চারটা লাইনের জন্য। লাইনগুলো হলো: মাঝখানে দুইটা space-ওয়ালা একটা বাক্য, একটা খালি লাইন, তিনটা space দিয়ে শুরু হওয়া একটা লাইন, আর save time। খালি লাইনে শব্দ 0টা, আর শুরুর space-গুলো কোনো শব্দ হিসেবে গোনা হয়নি। Template আর literals দুটোতেই 8টা character; > প্রথমে যেটা পায়, সেটাই রাখে।

Run in Compiler

Palindrome, দুই ভাবে

Palindrome মানে যেটা উল্টো দিক থেকে পড়লেও একই, যেমন noon। একটা বাক্যও palindrome হতে পারে, যদি বড় হাতের অক্ষর আর space বাদ দিয়ে দেখো। দেখার উপায় দুইটা: method দিয়ে text উল্টে মিলিয়ে দেখা, অথবা দুই মাথা থেকে ভিতরের দিকে হেঁটে জোড়ায় জোড়ায় মেলানো।

Example 2: একটা বাক্য, দুইবার যাচাই

নতুন যা: split(""), reverse() আর join("") এক শিকলে, আর সেই loop যেটা i ঘরকে মেলায় length - 1 - i ঘরের সাথে।

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 phrase = next();
const clean = phrase.toLowerCase().split(" ").join("");
const reversed = clean.split("").reverse().join("");

let same = true;
for (let i = 0; i < clean.length / 2; i++) {
  if (clean[i] !== clean[clean.length - 1 - i]) {
    same = false;
  }
}

out.push(`cleaned:    ${clean}`);
out.push(`reversed:   ${reversed}`);
out.push(`by reverse: ${clean === reversed ? "yes" : "no"}`);
out.push(`by loop:    ${same ? "yes" : "no"}`);

console.log(out.join("\n"));
cleaned:    neveroddoreven
reversed:   neveroddoreven
by reverse: yes
by loop:    yes

এই output-টা input Never odd or even-এর জন্য। split(" ").join("") space-গুলো সরিয়ে দেয়: প্রতিটা space-এ কাটো, তারপর কিছু না দিয়ে জোড়া দাও। Input Kenji was here দিলে দুইটা check-ই বলে no। Method-এর শিকলটা পড়তে ছোট। Loop-টা দ্বিতীয় কোনো string বানায় না, শুধু মাঝখান পর্যন্ত হাঁটে, আর এই idea-টাকেই lesson 06 two-pointer pattern বানাবে।

Run in Compiler

Character code দিয়ে Caesar shift

Caesar shift প্রতিটা অক্ষরকে বর্ণমালায় নির্দিষ্ট কয়েক ঘর সামনে সরিয়ে দেয়: shift 3 হলে a হয়ে যায় d। কৌশলটা হলো number নিয়ে কাজ করা। Lesson 02-এর code table-এ A থেকে Z আছে 65 থেকে 90-এ, আর a থেকে z 97 থেকে 122-এ। C track-এর ASCII table-এও ঠিক এই সংখ্যাগুলোই।

তাই code - 97 একটা ছোট হাতের অক্ষরকে বর্ণমালায় তার ঘর বানায়, 0 থেকে 25। Shift যোগ করো, % 26 নাও যাতে 26 ঘুরে 0 হয়ে যায়, তারপর 97 আবার যোগ করো। 13 ঘরের shift-এর নাম ROT13, আর এটা নিজেই নিজেকে উল্টে দেয়।

Example 3: ROT13, spoiler লুকানোর কৌশল

নতুন যা: প্রতিটা character-এর উপর loop চালিয়ে charCodeAt আর String.fromCharCode, বড় হাতের আর ছোট হাতের অক্ষর আলাদা করে সামলানো, আর বাকি সব হুবহু রেখে দেওয়া।

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 text = next();
  let result = "";
  for (let j = 0; j < text.length; j++) {
    const code = text.charCodeAt(j);
    if (code >= 65 && code <= 90) {
      result += String.fromCharCode((code - 65 + 13) % 26 + 65);
    } else if (code >= 97 && code <= 122) {
      result += String.fromCharCode((code - 97 + 13) % 26 + 97);
    } else {
      result += text[j];
    }
  }
  out.push(result);
}

console.log(out.join("\n"));
Jul qvq Obo pebff gur ebnq?
To get to the other side!

এই output-টা input 2, তারপর একটা ধাঁধা আর ROT13-এ লেখা তার উত্তরের জন্য। একই program প্রশ্নটা লুকিয়েছে, উত্তরটা খুলে দিয়েছে। Space আর ? যেমন ছিল তেমনই গেছে, কারণ ওদের code দুইটা range-এর কোনোটাতেই পড়ে না।

13-এর shift কখনো 0-এর নিচে নামে না। Negative shift নামতে পারে, আর তখন কী গড়বড় হয়, সেটা Module 1-এর %-এর নিয়ম থেকেই আন্দাজ করা যায়। নিচের brain teaser তোমাকে সেটা খুঁজে বের করতে বলবে, আর অনুশীলন 2-এ fix-টা লাগবে।

Run in Compiler

সাধারণ একটা CSV লাইন ভাঙা

CSV মানে comma-separated values: সাদামাটা text-এ একটা table, যেটা যেকোনো spreadsheet export করতে পারে। প্রতি লাইনে একটা record, field-গুলো comma দিয়ে আলাদা। সাধারণ লাইনের জন্য split(",")-ই পুরো parser। তারপর trim() সরিয়ে দেয় comma-র পরে মানুষ যে space টাইপ করে, সেগুলো।

Example 4: member list, আর কোথায় ভাঙে

নতুন যা: split(","), সাথে প্রতিটা field-এ trim(), আর এমন একটা record যেটা এই সাধারণ parser ভুল পড়ে।

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 parts = next().split(",");
  const fields = [];
  for (const part of parts) {
    fields.push(part.trim());
  }
  out.push(`${fields.length} fields: ${fields.join(" | ")}`);
}

console.log(out.join("\n"));
3 fields: Zara | 14 | Dhaka
3 fields: Bob | 15 | Sylhet
4 fields: "Maria" | 15 | "Flat 3 | Green Road"

এই output-টা input 3 আর উপরের তিনটা record-এর জন্য। প্রথম দুইটা ঠিক আছে, বাড়তি space থাকা সত্ত্বেও। তৃতীয়টা ভুল। কোনো field-এ comma থাকলে CSV সেটাকে double quote-এ রাখে, আর split quote-এর কিছুই বোঝে না, তাই Maria-র ঠিকানা দুইটা field হয়ে গেছে। Lesson 04 একটা loop দিয়ে এটা ঠিক করবে।

Run in Compiler

Slug বানানোর program

Slug হলো web address-এর সেই অংশ, যেটা একটা title থেকে বানানো: Hello, World! 2026 হয়ে যায় hello-world-2026। এতে থাকে শুধু ছোট হাতের অক্ষর আর digit, তাই যেকোনো address-এ এটা নিরাপদ। বাকি যেকোনো character-এর একটানা সারি হয়ে যায় একটা hyphen, আর দুই মাথায় কোনো hyphen থাকে না।

Slug বানানোর pipeline Title থেকে slug, এক ধাপ এক ধাপ করে "Hello, World! 2026" toLowerCase() "hello, world! 2026" a-z আর 0-9 রাখো; অন্য যেকোনো character শব্দ শেষ করে "hello" "world" "2026" শব্দগুলো join("-") "hello-world-2026" ", " আর "! " হলো অন্য character-এর সারি, তাই প্রতিটা শুধু একটা শব্দ শেষ করে। join শব্দের মাঝে hyphen বসায়, প্রথমটার আগে বা শেষটার পরে কখনো না।
Example 5: একটা title, প্রতিটা ধাপ print করে

নতুন যা: একটা character loop, যেটা অক্ষর আর digit-এর একটানা সারিগুলো শব্দ হিসেবে জমায়, তারপর join("-")। ch >= "a" && ch <= "z" test-টা কাজ করে, কারণ string-এর উপর < code মেলায়, lesson 02-এ যেমন দেখেছ।

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 title = next();
const lower = title.toLowerCase();

const words = [];
let word = "";
for (const ch of lower) {
  if ((ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9")) {
    word += ch;
  } else if (word !== "") {
    words.push(word);
    word = "";
  }
}
if (word !== "") {
  words.push(word);
}
const slug = words.join("-");

out.push(`title:     ${title}`);
out.push(`lowercase: ${lower}`);
out.push(`words:     ${words.join(" ")}`);
out.push(`slug:      ${slug}`);

console.log(out.join("\n"));
title:     Amara's Top 10 Books (2026)!
lowercase: amara's top 10 books (2026)!
words:     amara s top 10 books 2026
slug:      amara-s-top-10-books-2026

এই output-টা input Amara's Top 10 Books (2026)!-এর জন্য। শেষের if শেষ শব্দটা বাঁচায়, কারণ ওর পরে আর কোনো character নেই যে ওকে শেষ করবে। amara-s দেখো: আমাদের নিয়ম apostrophe-কে একটা ভাঙন বানিয়ে দিয়েছে। অনেক slug maker apostrophe-টা মুছেই ফেলে, সেটা আরেকটা নিয়ম মাত্র, নতুন কোনো idea না।

Run in Compiler

যে রসিদের কলাম সোজা থাকে

রসিদ আসলে সাদামাটা text-এ একটা table, আর প্রতিটা কলামের চওড়া নির্দিষ্ট হতে হয়। padEnd(16) একটা নামের পরে space বসিয়ে ওকে 16 character করে, আর padStart(8) একটা number-কে 8 ঘরের ডান কিনারায় ঠেলে দেয়। দুটোই string method, তাই number-কে আগে text বানাতে হয়।

Example 6: Amara-র রসিদ

নতুন যা: শুরুর জায়গা দিয়ে indexOf call করে দ্বিতীয় space খোঁজা, slice দিয়ে লাইনটাকে তিন টুকরো করা, লম্বা নাম ছোট করা, আর নির্দিষ্ট চওড়ার কলাম।

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 WIDTH = 34;
const n = nextInt();
let total = 0;
out.push("Amara's Book Corner".padStart(26));
out.push("-".repeat(WIDTH));
for (let i = 0; i < n; i++) {
  const line = next();
  const a = line.indexOf(" ");
  const b = line.indexOf(" ", a + 1);
  const qty = Number(line.slice(0, a));
  let name = line.slice(b + 1);
  const price = Number(line.slice(a + 1, b));
  if (name.length > 16) {
    name = name.slice(0, 15) + ".";
  }
  const cost = qty * price;
  total += cost;
  out.push(`${name.padEnd(16)}${`${qty} x ${price}`.padStart(10)}${String(cost).padStart(8)}`);
}
out.push("-".repeat(WIDTH));
out.push(`${"TOTAL (Tk)".padEnd(26)}${String(total).padStart(8)}`);

console.log(out.join("\n"));
       Amara's Book Corner
----------------------------------
Notebook A5        2 x 120     240
Fountain pen       1 x 450     450
Encyclopedia of.  1 x 1250    1250
----------------------------------
TOTAL (Tk)                    1940

এই output-টা input 3 আর তারপরের তিনটা item-এর জন্য; প্রতিটায় আছে পরিমাণ, টাকায় দাম আর একটা নাম। নামের ভিতরে space থাকতে পারে, তাই দ্বিতীয় space-এর পরের পুরোটাই নাম। `${qty} x ${price}` হলো আরেকটা template-এর ${}-এর ভিতরে বসা একটা template: আগে মাঝের কলামটা বানায়, তারপর pad করে। 26 character-এর নামটা কেটে 15 character আর একটা dot করা হয়েছে, তাই কলামগুলো এখনো সোজা।

Run in Compiler

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

  • WordPress। PHP-তে লেখা ওদের sanitize_title() function একটা post-এর title থেকে slug বানায়। Default-এ এটা call করে sanitize_title_with_dashes(), তাই "My Excellent Sentence" হয়ে যায় my-excellent-sentence: Example 5-এর idea-ই, অন্য একটা ভাষায়।
  • npm-এর slugify। এই JavaScript package slugify("some string")-কে বানায় some-string। lower: true না দিলে বড় হাতের অক্ষর রেখে দেয়, আর কিছু চিহ্নকে শব্দে বদলে ফেলে, যেমন ♥ হয়ে যায় love।
  • Papa Parse। JavaScript-এর একটা CSV parser, যেটা নিজের সম্পর্কে বলে CSV-র standard RFC 4180 অনুযায়ী ঠিকঠাক কাজ করে। Example 4 যে quote-এর ভেতরের comma আর line break সামলাতে পারে না, Papa Parse সেগুলো পারে।
  • Usenet আর Python-এ ROT13। 1980-এর দশকের শুরুতে Usenet newsgroup-এ মানুষ ধাঁধার উত্তর আর spoiler লুকাতে ROT13 ব্যবহার করত। Python-এর codecs module এখনো এটা রাখে, rot_13 text transform হিসেবে।

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

১. শুধু split(" ") দিয়ে শব্দ গোনা।

const line = "Amara writes  every day";
console.log(line.split(" "));
console.log(line.split(" ").length);
console.log(line.split(" ").filter(Boolean).length);
[ 'Amara', 'writes', '', 'every', 'day' ]
5
4

কোনো error নেই: দুইটা space মাঝখানে একটা খালি string রেখে যায়, আর সেটাও শব্দ হিসেবে গোনা হয়। .filter(Boolean) যোগ করো। এটা তুমি বাদ দেবে, কারণ তোমার নিজের test-এর বাক্যে space ছিল একটা করে, আর আসল text-এ সেটা কমই থাকে।

২. উল্টানো array-কে string-এর সাথে মেলানো।

const word = "anna";
const back = word.split("").reverse();
console.log(back);
console.log(back === word);
console.log(back.join("") === word);
[ 'a', 'n', 'n', 'a' ]
false
true

reverse() ফেরত দেয় একটা array, আর array কখনো একটা string-এর === হয় না। ফলে প্রতিটা শব্দকেই মনে হয় palindrome না। শিকলের শেষে join("") দাও। তুমি এটা বাদ দেবে, কারণ split আর reverse দেখেই মনে হয় কাজ শেষ।

৩. Number-এর উপর padStart call করা।

const cost = 240;
console.log("cost:", cost);
console.log("Notebook".padEnd(12) + cost.padStart(6));
cost: 240
TypeError: cost.padStart is not a function

Playground-এর verdict Runtime error, আর থামার আগে যে লাইন print হয়েছিল, সেটা থেকে যায়। padStart একটা string method, অথচ cost একটা number। লেখো String(cost).padStart(6), অথবা একটা template literal-কে pad করো। তুমি এটা ভুলবে, কারণ screen-এ number-টাকে text-এর মতোই দেখায়।

মাথা খাটাও

Bob Example 3-এর Caesar formula একটা shift k দিয়ে লেখে: (code - 97 + k) % 26 + 97। সে test করে z (code 122)-এর উপর k = 1 দিয়ে, আর a (code 97)-এর উপর k = -1 দিয়ে।

console.log(String.fromCharCode((122 - 97 + 1) % 26 + 97));
console.log(String.fromCharCode((97 - 97 - 1) % 26 + 97));

প্রথম লাইন print করে a, যেটা ঠিক। দ্বিতীয় লাইন z-এর বদলে কী print করে, আর একই formula এক দিকে কাজ করে অথচ অন্য দিকে করে না কেন?

ভাগশেষ কখন negative হয়, Module 1-এর সেই নিয়মটা মনে করে -1 % 26 বের করো। তারপর যে code পেলে, সেটা lesson 02-এর table-এ খুঁজে দেখো।

অনুশীলন ১সহজ

Zara ওর ক্লাসের জন্য একটা শব্দের খেলা বানাচ্ছে। খেলোয়াড়রা একটা একটা শব্দ টাইপ করে, আর খেলাটাকে বলতে হবে কোনগুলো palindrome। Zara-র প্রথম test হলো Level, কারণ সামনে বড় হাতের অক্ষর থাকলে কিছু বদলানো উচিত না।

Input. n, তারপর n-টা শব্দ। প্রতিটা শব্দ ASCII অক্ষর (a-z, A-Z) দিয়ে বানানো।

Output. প্রতিটা শব্দের জন্য এক লাইন: বড় হাতের আর ছোট হাতের অক্ষরকে একই ধরে উল্টো দিক থেকে পড়লেও একই হলে yes, নইলে no।

Constraints. 1 <= n <= 20000। প্রতিটা শব্দে 1 থেকে 50টা অক্ষর।

Sample. Input 5 আর Level noon Kenji Anna ab দিলে পাঁচ লাইনে yes, yes, no, yes আর no।

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
// Read n, then n words. Per word: yes or no.

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

palindromes নামে grade হয়। শব্দগুলোতে space নেই, তাই এটা token starter। Example 2-এর যেকোনো উপায়েই পাস করবে; যেটা তুমি বুঝিয়ে বলতে পারো, সেটা নাও।

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

Kenji বন্ধুদের Caesar shift দিয়ে গোপন message পাঠায়, আর ওর পছন্দ বড় বড় shift: 29, -3, এমনকি -1000। যেকোনো shift সামলাতে পারে, এমন program লেখো।

Input. লাইন 1-এ k। লাইন 2-এ text। এই problem starter-এর lines variant ব্যবহার করে, আর input-এর শেষ লাইন কখনো খালি না।

Output. Text-টা, যেখানে প্রতিটা অক্ষর বর্ণমালায় k ঘর সামনে সরেছে (k negative হলে পেছনে), শেষে গিয়ে আবার শুরুতে ঘুরে, বড় হাতের অক্ষর বড় হাতেরই থেকে। বাকি প্রতিটা character যেমন ছিল তেমন থাকে।

Constraints. -1000 <= k <= 1000। Text-এ 1 থেকে 100000টা printable ASCII character (code 32 থেকে 126)।

Sample. দুই লাইনে input 3 আর Hello, Zara! xyz দিলে Khoor, Cdud! abc।

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 k, then the text. Shift every letter k places.

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

caesar-shift নামে grade হয়। Example 3 থেকে শুরু করো। Hidden test-এ negative shift আছে, 26-এর অনেক বেশি shift-ও আছে, তাই আগে brain teaser-টা সমাধান করো।

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

Amara পুরো blog নতুন একটা host-এ সরাচ্ছে, আর পুরনো প্রতিটা post-এর একটা slug লাগবে। কিছু title-এ কোনো অক্ষরই নেই, শুধু চিহ্ন, অথচ ওই post-গুলোরও একটা address লাগবে।

Input. n, তারপর n-টা title, প্রতি লাইনে একটা। এই problem starter-এর lines variant ব্যবহার করে, আর input-এর শেষ লাইন কখনো খালি না।

Output. প্রতিটা title-এর জন্য এক লাইনে তার slug। বানানোর নিয়ম: title-টা ছোট হাতের করো। a-z বা 0-9 না, এমন character-এর প্রতিটা সবচেয়ে লম্বা একটানা সারি হয়ে যায় একটা hyphen, আর শুরুতে বা শেষে কোনো hyphen থাকে না। কিছুই না বাঁচলে print করো untitled শব্দটা।

Constraints. 1 <= n <= 10000। প্রতিটা title-এ 1 থেকে 200টা printable ASCII character (code 32 থেকে 126)।

Sample. Input 3, তারপর তিন লাইনে Hello, World! 2026, --- Strings & Template Literals --- আর !!! দিলে hello-world-2026, strings-template-literals আর untitled।

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 titles. Per title: its slug, or untitled.

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

slug-maker নামে grade হয়। Example 5 একটা title সামলায়; এখানে title অনেক, আর কয়েকটায় একটা শব্দও বাঁচে না।

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

Amara-র editor phone-এর জন্য প্রতিটা post-এর একটা সরু preview দেখায়। Preview text-টাকে এমনভাবে ভাঙে যাতে কোনো লাইন screen-এর চেয়ে চওড়া না হয়, আর কোনো শব্দ কখনো মাঝখান থেকে কাটা না পড়ে।

Input. লাইন 1-এ চওড়া w। লাইন 2-এ text, শব্দগুলোর মাঝে এক বা একাধিক space।

Output. শব্দগুলো ক্রম ঠিক রেখে লাইনে লাইনে সাজানো। প্রতিটা লাইনে যতগুলো শব্দ ধরে ততগুলো, মাঝে একটা করে space, আর লাইনের দৈর্ঘ্য বড়জোর w character। w-এর চেয়ে লম্বা কোনো শব্দ পায় নিজের আলাদা একটা লাইন।

Constraints. 10 <= w <= 80। Text-এ 1 থেকে 1000টা printable ASCII character, আর অন্তত একটা শব্দ।

Sample. Input 16 আর Amara writes every day, and her readers wait for Friday. দিলে চার লাইনে Amara writes, every day, and, her readers wait আর for Friday.।

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 হয় না। যে লাইনটা বানাচ্ছ সেটা একটা variable-এ রাখো, আর প্রতিটা শব্দের জন্য একটাই প্রশ্ন করো: সামনে একটা space সহ এটা এখনো ধরে কি? Zara-র test: 10 চওড়ায় supercalifragilistic, আর ঠিক w character-এর একটা লাইন।

Run in Compiler

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

  • Palindrome check কি বাংলা শব্দে কাজ করে?

    Zara চেষ্টা করে দেখেছে। "কি".split("").reverse().join("") দেয় "িক": কার-চিহ্নটা লাফিয়ে ব্যঞ্জনের সামনে চলে যায়। অক্ষর আর তার কার-চিহ্ন মিলে দুইটা code unit, আর split("") ঠিক ওদের মাঝখানে কাটে। পাঠক যে character দেখে, সেগুলো নিয়ে কাজ করবে lesson 05।

  • Loop-এর ভিতরে result += কি slow?

    এই সাইজে না। প্রতিটা += একটা নতুন string বানায়, তবু মানুষ যত copy-র ভয় পায়, V8 তার বেশিরভাগটাই এড়িয়ে যায়। Lesson 05 (Pro) এটাকে একটা array আর একবারের join-এর সাথে মেপে দেখায়।

  • Slug বানাতে regular expression ব্যবহার করি না কেন?

    করতেই পারো। Regular expression হলো text-এর জন্য ছোট একটা pattern-এর ভাষা, আর Module 16 এটা শেখাবে। Loop-টা আগে প্রতিটা সিদ্ধান্ত সাদামাটা code-এ দেখায়, আর lesson 04 দেখাবে কখন একটা কাজ pattern হয়ে যায়।

  • ROT13-এর আলাদা decoder লাগে না কেন?

    কারণ 13 + 13 = 26, বর্ণমালার এক পুরো চক্কর। অন্য যেকোনো shift k উল্টাতে লাগে 26 - k ঘরের shift।

মূল কথা

  • Input space-ওয়ালা text হলে starter-এর lines variant নাও; তখন next() পুরো একটা লাইন ফেরত দেয়, খালি লাইনও।
  • split(" ").filter(Boolean) একাধিক space আর খালি লাইন পেরিয়েও ঠিকঠাক শব্দ গোনে; split, reverse আর join ASCII text উল্টায়।
  • charCodeAt, একটু অঙ্ক আর String.fromCharCode মিলে Caesar shift; % 26 ঘুরিয়ে আনে, আর negative shift-এ আরও এক ধাপ লাগে।
  • split(",") শুধু সাধারণ CSV পড়তে পারে: quote-এর ভেতরে একটা comma থাকলেই ভাঙে।
  • padEnd আর padStart-সহ template literal সোজা কলাম print করে; pad করো string-কে, কখনো number-কে না।
  • আরও গভীরে: CP and Interview Pack, string-এর pattern, প্রশ্ন আর bug gallery (Pro)।

এরপর lesson 04 quote বোঝে এমন একটা loop দিয়ে Kenji-র CSV import ঠিক করে। তারপর judgement call-গুলো নেয়: method না loop, template না +, আর কখন কাজটা regular expression হয়ে গেছে।

lesson ৩ শেষ

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

পরেরটা: কোনটা কখন: method না loop, template না +, আর কখন regex

পুরো program: কাজের মাঝে string | Learn JavaScript | Progsity