Learn JavaScript

lesson ৭ / ৯ · string আর template literal

Module ৩ · string আর template literal

Problem: string

FreeProblem

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

  • প্রতিটা problem-এর জন্য ঠিক reader বাছতে পারবে: আলাদা আলাদা শব্দের জন্য token starter, আর space বা ফাঁকা লাইনওয়ালা text-এর জন্য lines variant।
  • charCodeAt, String.fromCharCode, একটা count array বা একটা flag দিয়ে string-এর ভেতর দিয়ে একটা একটা character করে হাঁটতে পারবে, আর += বা template literal দিয়ে উত্তরের লাইন বানাতে পারবে।
  • Submit করার আগে hidden test-এর কিনারাগুলো নিজেই চালিয়ে দেখতে পারবে: ফাঁকা লাইন, পরপর কয়েকটা space, negative shift, emoji আর quote-এর ভেতরের কমা।

দশটা problem, Node 22-এ hidden test দিয়ে grade হয়, মানে Playground যে JavaScript চালায় সেটাই। প্রতিটার সাথে এই module-এর lesson-গুলোতে exercise হিসেবে তোমার দেখা হয়ে গেছে। এখানে সবগুলো একসাথে, lesson 02-এর শব্দ গোনা থেকে lesson 06-এর run-length রূপ পর্যন্ত।

Problem 1, 2 আর 4 (word-count, palindromes আর initials) সবার জন্য free। বাকি সাতটা Pro problem: প্রতিটা problem আলাদা করে lock করার ব্যবস্থা আসবে পরের একটা release-এ, আর তার আগ পর্যন্ত দশটাই খোলা। Module test-এ দুটো problem আছে, palindromes আর caesar-shift, তাই এ দুটো এখানে আগে সমাধান করে রাখা কাজের।

Bob sample পড়ে, sample যা print করে এমন প্রথম program-টা লিখেই submit করে দেয়। Zara আগে নিজের program-কে খাওয়ায় একটা ফাঁকা লাইন, পরপর তিনটা space আর একটা emoji। এই set-এর hidden test ঠিক এগুলো দিয়েই বানানো, তাই নম্বর আনে Zara-র অভ্যাসটাই।

Token starter নাকি lines variant

যে starter তুমি চেনো, সেটা পুরো input-কে token-এ ভাগ করে। Token হলো space বা লাইন-ভাঙার মাঝখানের এক টুকরো text, তাই একটা token-এ কখনো space থাকে না। এখানে চারটা problem-এর input আলাদা আলাদা শব্দ, ওদের জন্য এটাই ঠিক: palindromes, anagram-check, first-unique আর run-length।

বাকি ছয়টা এমন text পড়ে, যেখানে space-এর দাম আছে বা লাইন ফাঁকা হতে পারে। Token-এ ভাগ করলে দুটোই হারায়। .filter(Boolean) প্রতিটা ফাঁকা string ফেলে দেয়, তাই ফাঁকা লাইন উধাও হয়, আর তোমার code দেখার আগেই space-গুলো চলে যায়। তাই এই ছয়টা ব্যবহার করে lines variant। Starter-এর দুটো লাইন বদলায়: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে।

এখন next() দেয় একটা পুরো লাইন, space-সহ, আর ফাঁকা লাইন আসে "" হয়ে। n যে লাইনে আছে, সেখানে nextInt() আগের মতোই কাজ করে, কারণ শুধু একটা সংখ্যাওয়ালা লাইন Number() ঠিকই পড়ে। নিচের ছবিটা word-count-এর sample-কে lines variant-এর ভেতর দিয়ে অনুসরণ করে।

word-count-এর sample, lines variant দিয়ে পড়া একটা input, লাইন ধরে ধরে পড়া stdin: একটাই string lines: একটা array লাইনপ্রতি শব্দ 4\n Amara·writes·· every·day\n \n ··hello\n one·two·three·four\n input.split("\n") 0 "4" 1 "Amara·writes··every·day" 2 "" 3 "··hello" 4 "one·two·three·four" 5 "", শেষ \n-এর পরে 4টা শব্দ 0টা শব্দ 1টা শব্দ 4টা শব্দ split(" ").filter(Boolean) একটা dot · মানে একটা space। nextInt() বাক্স 0-কে সংখ্যা 4 বানায়, তারপর প্রতিটা next() দেয় একটা পুরো বাক্স: space যেমন ছিল, ফাঁকা লাইন হলে ""। Loop পড়ে 4টা লাইন, তাই বাক্স 5, শেষ newline-এর পরের text, কখনো পড়া হয় না। Token-এ ভাগ করলে এখানে হতো 10টা token, আর কোনো লাইনই আর ফাঁকা থাকত না।

Input-এ যত লাইন, array-তে বাক্স তার চেয়ে একটা বেশি, কারণ শেষ newline-এর পরের text-টা একটা ফাঁকা string। তোমার loop ঠিক n-টা লাইন পড়ে, ওটা ছোঁয়ও না। এজন্যই lines variant-এর প্রতিটা statement কথা দেয়, শেষ লাইনটা কখনো ফাঁকা হবে না। তাহলে শেষের ফাঁকা লাইন কীভাবে পড়া হলো, তার ওপর কোনো উত্তর নির্ভর করে না।

Judge text মেলায়, তাই প্রতিটা character-এর দাম আছে

Judge একটা hidden input দিয়ে তোমার পুরো program চালায়, আর program যা print করে সেটা লাইন ধরে ধরে মেলায়। লাইনের শেষে বাড়তি space থাকলে judge সেটা ধরে না, একদম শেষে ফাঁকা লাইন থাকলেও না। বাকি সব হুবহু মিলতে হবে। Statement যেখানে ছোট হাতের অক্ষর লিখেছে সেখানে বড় হাতের অক্ষর, একটা dot বাদ পড়া, বা লাইনের শুরুতে একটা space, এর যেকোনোটাই ভুল উত্তর।

এই set-এর প্রতিটা উত্তর তুমি নিজে text হিসেবে বানাও, তাই statement-ই তোমার একমাত্র পথ দেখানোর জিনিস। yes আর Yes এক না, A.L. আর A. L. এক না, আর শেষে hyphen-ওয়ালা slug একটা আলাদা slug। প্রতিটা লাইন বানাও, out-এ push করো, আর সবগুলো print করার কাজটা ছেড়ে দাও starter-এর একমাত্র console.log-এর হাতে।

Hidden test যে কিনারাগুলো ধরে

প্রতিটা problem-এ 11 থেকে 16টা hidden test। এরা চলে সবচেয়ে ছোট input থেকে constraints যতটা বড় হতে দেয় ততটা পর্যন্ত, আর প্রতিটা test file 1 MiB-এর নিচে থাকে। এই দুইয়ের মাঝখানে বসে আছে এমন প্রতিটা string, যাকে এই module তোমাকে সন্দেহ করতে শিখিয়েছে। Table-টা ওদের নাম বলে দেয়, যাতে তুমি আগেই ওগুলো চালিয়ে দেখতে পারো।

ProblemAccessTestHidden test যা চালায়
word-countFree13ফাঁকা লাইন, শুরুতে, মাঝে আর শেষে পরপর কয়েকটা space, 300টা space-এর একটা লাইন, এক অক্ষরের শব্দ, শব্দের গায়ে লেগে থাকা যতিচিহ্ন, 20000 লাইন, পরপর 19999টা ফাঁকা লাইন
palindromesFree12এক আর দুই অক্ষরের শব্দ, rAcEcAr-এর মতো বড়-ছোট মেশানো অক্ষর, শুধু একদম মাঝের জোড়ায় ভাঙা palindrome, 50 অক্ষরের 20000টা শব্দ
caesar-shiftPro16k = 0, 13, 25, 26, 52, -1, -13, -26, -27, 1000 আর -1000, একটাও অক্ষর নেই এমন text, Z আর a-র মাঝের ছয়টা character, 100000টা character
initialsFree11এক অক্ষরের শব্দ, 20 অক্ষরের ছয়টা শব্দ, সব বড় হাতের আর বড়-ছোট মেশানো নাম, 20000টা নাম
slug-makerPro12একটাও অক্ষর বা অঙ্ক নেই এমন title, underscore, দুই মাথায় টানা সারি, code-এর table-এ a থেকে z আর 0 থেকে 9-এর ঠিক পাশের character-গুলো, 200 character-এর title, 10000টা title
csv-linePro14ফাঁকা record, 500টা কমার একটা record, quote-এর ভেতরে ফাঁকা field "", একটা quote বোঝাতে """", field-এর আশেপাশে space, field-এর ভেতরে bracket, 500 character-এর record
true-lengthPro14ফাঁকা লাইন, বাংলা য়-এর দুই বানান, ZWJ দিয়ে জোড়া family emoji, একটা পতাকা, একটা skin tone, combining accent, শুধু ASCII-র লাইন, 1000টা emoji আর U+FFFF-এর উপরের অন্য character-এর লাইন
anagram-checkPro12আলাদা দৈর্ঘ্যের শব্দ, character code-এর যোগফল সমান এমন জোড়া, 100000 অক্ষরের দুইটা শব্দ, এক অক্ষরের 200000টা জোড়া
first-uniquePro12একবার আছে এমন কোনো অক্ষরই নেই, এমন অক্ষর একদম শেষে, এমন কয়েকটা অক্ষর, 100000 অক্ষরের শব্দ, দুই অক্ষরের 200000টা শব্দ
run-lengthPro13AAaa আর ছোট হাতের পাশে বড় হাতের অক্ষরের আরও উদাহরণ, সমান দৈর্ঘ্যের রূপ, 9, 10, 99, 100, 999 আর 1000-এর সারি, 100000-এর একটা সারি, এক অক্ষরের 300000টা শব্দ

ছোট কিনারাগুলো Playground-এ চালিয়ে দেখো। ওর stdin বাক্সে সর্বোচ্চ 10000টা character ধরে, তাই সবচেয়ে বড় test-গুলো ওখানে আঁটে না। এই সীমাটা বাক্সের, judge-এর না। তাছাড়া একটা কিনারা নিয়ে নিজে লেখা একটা test বড় test-এর চেয়ে বেশি কথা বলে।

এই দশটা problem-এ যে রূপগুলো লাগে

const line = next();               lines variant: পুরো একটা লাইন, ফাঁকা হলে ""
const word = next();               token starter: একটা শব্দ, কখনো space না
line.split(" ").filter(Boolean)    একটা লাইনের শব্দগুলো, পরপর space বাদ
s.toLowerCase()                    ছোট হাতের অক্ষরে নতুন string; s বদলায় না
s[0].toUpperCase()                 প্রথম character, বড় হাতে
s.charCodeAt(i)                    unit i-এর code: "A" হলো 65, "a" হলো 97
String.fromCharCode(code)          একটা code-এর এক-character-এর string
a % b                              a-এর চিহ্ন রাখে: -1 % 26 হলো -1
s.length                           code unit; [...s].length গোনে code point
counts[s.charCodeAt(i) - 97]++     প্রতি অক্ষরে একটা বাক্স, "a" থেকে "z"
`${letter}${count}`                template literal: value জুড়ে একটা string
  • নিচের প্রতিটা starter এই track-এর নির্দিষ্ট starter, বা problem যেখানে বলে সেখানে ওর lines variant। ওর লাইনগুলো যেমন আছে রাখো, আর নিজের code লেখো comment যেখানে বলে সেখানে।
  • প্রতিটা problem-এর জন্য একটা সাধারণ for loop আর if-ই যথেষ্ট। ওদের সাথে দেখা হয়েছে Module 1-এর lesson 05-এ, আর শেখাবে Module 4।
  • প্রতিটা উত্তরের জন্য output-এর একটা লাইন push করো, statement যেভাবে লেখে হুবহু সেভাবে।
Example 1: একই input, token হিসেবে আর লাইন হিসেবে

Zara একটা program-কে চারটা লাইন দেয়: সংখ্যা 3, মাঝে দুইটা space রেখে দুইটা শব্দ, একটা ফাঁকা লাইন, আর একটা space-এর পরে দুইটা শব্দ। Program-টা lines variant চালায়, আবার একই input-কে token-এও ভাগ করে, যাতে দুই reader পাশাপাশি দেখা যায়।

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 tokens = input.split(/\s+/).filter(Boolean);
out.push(`tokens: ${tokens.length}`);
out.push(`lines: ${lines.length}`);
for (let i = 0; i < lines.length; i++) {
  out.push(`${i} [${lines[i]}]`);
}

console.log(out.join("\n"));
tokens: 5
lines: 5
0 [3]
1 [Zara  tests]
2 []
3 [ edge cases]
4 []

এই output input 3, তারপর Zara tests, একটা ফাঁকা লাইন আর সামনে একটা space-সহ edge cases-এর জন্য, শেষে একটা newline, judge-এর প্রতিটা test-এর মতো। Bracket দুটো প্রতিটা লাইন হুবহু দেখায়। লাইন 1 ওর দুইটা space রেখে দেয়, লাইন 2 হলো ফাঁকা লাইনটা, আর লাইন 3 ওর শুরুর space রেখে দেয়।

পাঁচটা token হলো 3, Zara, tests, edge আর cases: ফাঁকা লাইন আর প্রতিটা space উধাও। বাক্স 4 হলো শেষ newline-এর পরের ফাঁকা string, n লাইনের কোনো loop যে বাক্সে কখনো পৌঁছায় না। Playground-এর stdin বাক্সে শেষ লাইনের পরে Enter না দিলে input-এর শেষে কোনো newline থাকে না, তখন বাক্স 4-ও থাকে না।

Run in Compiler
Example 2: অক্ষর আসলে সংখ্যা, আর Z + 1 মানে A না

Alice ওর Caesar চিরকুটের পেছনের code-গুলো দেখতে চায়। Program-টা একটা শব্দ পড়ে, আর প্রতিটা character, ওর code আর ওর পরের code-এর character print করে।

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 = [];

const word = next();

for (let i = 0; i < word.length; i++) {
  const code = word.charCodeAt(i);
  out.push(`${word[i]} ${code} ${String.fromCharCode(code + 1)}`);
}

console.log(out.join("\n"));
A 65 B
Z 90 [
a 97 b
z 122 {
@ 64 A
[ 91 \
` 96 a
{ 123 |

এই output input AZaz@[`{-এর জন্য। বড় হাতের অক্ষর 65 থেকে 90, ছোট হাতের 97 থেকে 122, lesson 02-এর ASCII table। Z-এর এক code পরে [, A না, তাই shift-কে ঘুরিয়ে আনার কাজটা নিজেকেই করতে হয়।

Z আর a-র মাঝে বসে আছে 91 থেকে 96, এই ছয়টা code। ch >= "A" && ch <= "z" দিয়ে অক্ষর check করলে এই ছয়টাও ঢুকে পড়ে, আর caesar-shift-এ ঠিক এটার জন্যই একটা hidden test আছে।

Run in Compiler
Example 3: ছাব্বিশটা বাক্স, প্রতিটা অক্ষরের জন্য একটা

Kenji প্রতিটা শব্দের অক্ষর গোনে 26টা সংখ্যার একটা array দিয়ে, প্রতিটা অক্ষরের জন্য একটা বাক্স। কোন বাক্স, সেটা বাছে charCodeAt(j) - 97: a-র জন্য 0, z-এর জন্য 25। Program-টা print করে সেই সব অক্ষর, যাদের বাক্স 0 না।

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 = [];

const n = nextInt();

for (let i = 0; i < n; i++) {
  const word = next();
  const counts = [];
  for (let c = 0; c < 26; c++) {
    counts.push(0);
  }
  for (let j = 0; j < word.length; j++) {
    counts[word.charCodeAt(j) - 97]++;
  }
  let line = word + ":";
  for (let c = 0; c < 26; c++) {
    if (counts[c] > 0) {
      line += ` ${String.fromCharCode(97 + c)}${counts[c]}`;
    }
  }
  out.push(line);
}

console.log(out.join("\n"));
banana: a3 b1 n2
kenji: e1 i1 j1 k1 n1

এই output input 2 আর banana kenji-এর জন্য। অক্ষরগুলো বের হয় বর্ণমালার ক্রমে, কারণ program হাঁটে বাক্সগুলোর ওপর দিয়ে, শব্দের ওপর দিয়ে না। শব্দ যত লম্বাই হোক, বাক্সগুলো ভরতে লাগে একটাই pass।

দুইটা problem এই বাক্সগুলোর ওপর দাঁড়িয়ে। anagram-check একটা শব্দ দিয়ে বাক্স ভরে, অন্যটা দিয়ে খালি করে। first-unique আগে বাক্স ভরে, তারপর শব্দটার ওপর দিয়েই হাঁটে, কারণ ওর দরকার শব্দের ক্রম, বর্ণমালার না।

Run in Compiler

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

  • GNU wc -w. Linux-এর সাথে আসা শব্দ গোনার এই tool white space-এর মাঝের character-এর টানা সারিকে একটা শব্দ ধরে, ঠিক word-count-এর নিয়ম। Sample-এর text-এর চারটা লাইনে চালালে ও print করে 9, মানে 4, 0, 1 আর 4-এর যোগফল।
  • RFC 4180, CSV-র standard. যে field-এ কমা, double quote বা লাইন-ভাঙা আছে, সেটা double quote-এর ভেতরে লেখার কথা। ভেতরের একটা quote লেখা হয় দুইবার। Python-এর csv module "12, Lake Road",Amara-কে পড়ে দুইটা field হিসেবে, csv-line-এর মতোই।
  • Python-এ ROT13. ROT13 হলো 13 ঘরের Caesar shift, spoiler লুকাতে ব্যবহার হয়। Python-এর codecs module এটাকে একটা codec হিসেবে দেয়: codecs.encode("Hello, Zara!", "rot13") দেয় Uryyb, Mnen!।

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

১. split(" ")-এর টুকরোগুলোকেই শব্দ ধরে গোনা।

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 line = next();
  out.push(String(line.split(" ").length));
}

console.log(out.join("\n"));
5
1
3

Input 3, তারপর Amara writes every day, একটা ফাঁকা লাইন আর hello দিলে output এটাই। ঠিক গোনা হলো 4, 0 আর 1। split(" ") প্রতিটা একক space-এ কাটে, তাই পরপর দুইটা space-এর মাঝে একটা ফাঁকা টুকরো থেকে যায়, আর একটা ফাঁকা লাইন ভাগ হয়ে হয় [""], একটা টুকরো। ভুলটা তুমি করবে, কারণ হাতে টাইপ করা প্রতিটা test-এ space থাকে একটা একটা করে। .filter(Boolean) জুড়ে দাও।

২. Negative shift-এ %।

const k = -1;
const code = "a".charCodeAt(0);
const moved = ((code - 97 + k) % 26) + 97;

console.log(moved, String.fromCharCode(moved));
console.log(-1 % 26, ((-1 % 26) + 26) % 26);
96 `
-1 25

কোনো error নেই, অথচ a-কে এক ঘর পেছনে সরালে z-এর বদলে print হয় একটা backtick। JavaScript-এ % বাঁ দিকের সংখ্যার চিহ্ন রেখে দেয়, তাই -1 % 26 হলো -1, 25 না। ভুলটা তুমি করবে, কারণ সামনের দিকের প্রতিটা shift ঠিকঠাক চলে। ((k % 26) + 26) % 26 যেকোনো k-কে 0 থেকে 25-এর একটা সামনের shift বানিয়ে দেয়।

৩. CSV record-কে প্রতিটা কমায় কাটা।

const record = "\"12, Lake Road\",Amara";
const fields = record.split(",");

console.log(fields.length);
console.log(fields);
3
[ '"12', ' Lake Road"', 'Amara' ]

Record-এ field দুইটা, আর split(",") পায় তিনটা, address-এর দুই অর্ধেকের গায়ে একটা করে quote লেগে আছে। split শুধু নিজের separator চেনে, quote-এর কিছুই জানে না। ভুলটা তুমি করবে, কারণ বেশিরভাগ test record-এ কোনো quote-ই থাকে না। Record-এর ভেতর দিয়ে হাঁটো একটা flag নিয়ে, যেটা বলে তুমি quote-এর ভেতরে আছ কি না।

৪. toUpperCase() call করে পুরোনো string-টাই রেখে দেওয়া।

const word = "ada";
word.toUpperCase();

console.log(word[0] + ".");
console.log(word[0].toUpperCase() + ".");
a.
A.

লাইন 2 "ADA" বানায় আর ফেলে দেয়, কারণ string কখনো নিজের জায়গায় বদলায় না: প্রতিটা method একটা নতুন string return করে। তাই word তখনো "ada", আর প্রথম initial print হয় ছোট হাতে। ভুলটা তুমি করবে, কারণ call-টা পড়লে মনে হয় শব্দটাকে একটা হুকুম দেওয়া হলো। Method যা return করে, সেটাই ব্যবহার করো।

মাথা খাটাও

Amara একই দিনে তিনটা post ছাপে। ওদের title C++ Tips, C# Tips আর C Tips!।

কিছু না চালিয়ে বলো: slug-maker প্রতিটা title-কে কোন slug দেয়? তিনটাই Amara-র blog-এ চালু হলে কী গোলমাল হয়, আর ঠিক করতে তুমি কী বদলাতে?

প্রতিটা title ছোট হাতের অক্ষরে লেখো, তারপর a থেকে z বা 0 থেকে 9 নয় এমন প্রতিটা character দাগিয়ে দাও। Slug একটা web address-এর অংশ।

Problem 1: word-countসহজFree

Amara-র blog editor প্রতিটা paragraph-এর নিচে দেখায় ওতে কয়টা শব্দ আছে। Bob-এর প্রথম version দুইটা space-ওয়ালা একটা লাইনে গুনেছিল 5টা শব্দ, আর একটা ফাঁকা লাইনে 1টা। Amara এমন counter চায় যেটা প্রতিটা লাইনে ঠিক গোনে, space যেখানেই পড়ুক। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, ফাঁকা লাইনসহ। n পড়ো nextInt() দিয়ে।

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

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

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

Sample. Input 4, তারপর Amara writes every day (writes-এর পরে দুইটা space), একটা ফাঁকা লাইন, hello (সামনে দুইটা space) আর one two three four দিলে চার লাইনে 4, 0, 1 আর 4।

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"));
Run in Compiler

Hint 1

"a b".split(" ") কী দেয়? শব্দ গোনার আগে ওর টুকরোগুলো গুনে দেখো।

Hint 2

প্রতিটা লাইন পড়ো next() দিয়ে। একটা space-এ ভাগ করো, ফাঁকা টুকরোগুলো ফেলে দাও, আর যতগুলো টুকরো বাকি থাকে সেই সংখ্যাটা push করো।

Solution

line.split(" ") প্রতিটা space-এ কাটে। পরপর দুইটা space-এর মাঝে থেকে যায় একটা ফাঁকা string, আর দুই মাথার যেকোনোটায় space থাকলে সেখানেও একটা। .filter(Boolean) শুধু ফাঁকা নয় এমন টুকরোগুলো রাখে, আর ঠিক ওগুলোই শব্দ। একটা ফাঁকা লাইন ভাগ হয়ে হয় [""], filter-এর পরে কিছুই থাকে না, তাই গোনা হয় 0।

Filter ছাড়া sample-এর প্রথম লাইনে গোনা হয় 5 আর ফাঁকা লাইনে 1, উপরের ভুল ১ যেমন দেখায়। Token starter লাইন-ভাঙাগুলো হারিয়ে ফেলে, তাই এক লাইনের শব্দ কোথায় শেষ, সেটা আর বোঝার উপায় থাকে না। Hidden test-এ আরও আছে 300টা space-এর একটা লাইন, শব্দের গায়ে লেগে থাকা যতিচিহ্ন আর পরপর 19999টা ফাঁকা লাইন।

Problem 2: palindromesসহজFree

Zara palindrome জমায়, মানে এমন শব্দ যেটা উল্টো দিক থেকে পড়লেও একই, যেমন noon। ওর list-এ বড় হাতের আর ছোট হাতের অক্ষর মেশানো, আর ও চায় Level-ও গোনা হোক: পড়তে গেলে L আর l তো একই অক্ষর। Input পড়ো starter-এর nextInt() আর next() দিয়ে।

Input. প্রথম লাইনে n। দ্বিতীয় লাইনে n-টা শব্দ, মাঝে একটা করে space। প্রতিটা শব্দ ASCII অক্ষর a থেকে z আর A থেকে Z দিয়ে বানানো।

Output. n-টা লাইন, প্রতিটা শব্দের জন্য একটা, input-এর ক্রমে: বড় হাতের আর ছোট হাতের অক্ষরকে একই ধরলে শব্দটা উল্টো দিক থেকেও একই পড়া গেলে 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"));
Run in Compiler

Hint 1

Level আর level-এর উত্তর একই হতে হবে। কোন একটা call ওদের একই শব্দ বানিয়ে দেয়?

Hint 2

শব্দটা একবার ছোট হাতের অক্ষরে নাও। তারপর ওটাকে ওর নিজের উল্টো রূপের সাথে মেলাও, নয়তো দুই মাথা থেকে দুইটা index ভেতরের দিকে হাঁটিয়ে ওরা যে অক্ষর দেখায় সেগুলো মেলাও।

Solution

toLowerCase()-এর পরে palindrome মানে এমন শব্দ, যেটা নিজের উল্টো রূপের সমান। word.split("").reverse().join("") উল্টো রূপটা বানায়: অক্ষরগুলো একটা array-তে, array-টা উল্টে, অক্ষরগুলো আবার জোড়া। এখানের শব্দগুলো শুধু ASCII অক্ষর, তাই split("") নিরাপদ; emoji-র বেলায় কেন নিরাপদ না, সেটা দেখায় lesson 04।

দুই মাথা থেকে চলা loop নতুন string না বানিয়েই একই কাজ করে: i যতক্ষণ প্রথম অর্ধেকে, ততক্ষণ word[i]-কে মেলাও word[word.length - 1 - i]-এর সাথে। Bob মেলায় word[word.length - i]-এর সাথে, যেটা প্রথম ধাপেই undefined, তাই ও প্রতিটা শব্দকে no বলে। toLowerCase() ছাড়া মেলালে Level পায় no, আর sample সেটা ধরে ফেলে।

Problem 3: caesar-shiftমাঝারিPro

Alice আর Zara নিজেদের মধ্যে Caesar code-এ চিরকুট চালাচালি করে: প্রতিটা অক্ষর বর্ণমালা ধরে k ঘর সরে যায়, আর বর্ণমালা ঘুরে আসে, মানে z-এর পরে আবার a। Shift বাছে Zara, আর কী ভাঙে দেখতে ও প্রায়ই negative বা 26-এর অনেক বেশি একটা সংখ্যা বেছে নেয়। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, space-সহ। k পড়ো nextInt() দিয়ে।

Input. প্রথম লাইনে পূর্ণসংখ্যা k। দ্বিতীয় লাইনে text।

Output. এক লাইন: text-টা, যেখানে প্রতিটা অক্ষর বর্ণমালায় k ঘর সামনে সরেছে, আর k negative হলে পেছনে। সরাটা ঘুরে আসে: সামনে গেলে z থেকে a-তে, পেছনে গেলে a থেকে z-এ। বড় হাতের অক্ষর বড় হাতেরই থাকে, ছোট হাতের অক্ষর ছোটই। বাকি সব অক্ষর, space-ও, যেমন ছিল তেমনই থাকে।

Constraints. -1000 <= k <= 1000। Text-এ 1 থেকে 100000টা printable ASCII অক্ষর (code 32 থেকে 126)। Input-এর শেষ লাইনটা কখনো ফাঁকা না, আর প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

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"));
Run in Compiler

Hint 1

ছোট হাতের অক্ষরগুলোকে 0 থেকে 25 নম্বর দাও। Shift মানে k যোগ করা আর 26-এ ঘুরিয়ে আনা। JavaScript-এ -1 % 26 কী দেয়, আর কী দেওয়া উচিত?

Hint 2

Loop-এর আগে একবারই k-কে 0 থেকে 25-এর একটা সামনের shift বানাও। তারপর প্রতিটা character-এর code মেলাও বড় হাতের অক্ষরের সীমা আর ছোট হাতের অক্ষরের সীমার সাথে। অক্ষরকে সরাও ওর নিজের সীমার ভেতরেই, আর বাকি সব character যেমন আছে রাখো।

Problem 4: initialsসহজFree

David একটা coding camp-এর জন্য name badge print করে, আর প্রতিটা badge-এর এক কোণে থাকে মানুষটার নামের initial। সবাই নিজের নাম টাইপ করে যার যেমন খুশি বড়-ছোট হাতের অক্ষরে: ada lovelace, ZARA, kEnJi। Badge-এ কিন্তু সবসময় বড় হাতের অক্ষর। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, মানে একটা পুরো নাম। n পড়ো nextInt() দিয়ে।

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

Output. n-টা লাইন, প্রতিটা নামের জন্য একটা: প্রতিটা শব্দের প্রথম অক্ষর বড় হাতের করে, প্রতিটার পরে একটা 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.।

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"));
Run in Compiler

Hint 1

Grace Brewster Murray Hopper-এ এক লাইনে চারটা শব্দ। শব্দগুলোর মাঝে একটা করে space থাকলে একটা লাইনের শব্দগুলো কীভাবে পাবে?

Hint 2

লাইনটা একটা space-এ ভাগ করো। প্রতিটা শব্দের প্রথম character নাও, বড় হাতের করো, আর ওটা আর একটা dot জুড়ে দাও যে লাইনটা বানাচ্ছ তার সাথে।

Solution

Statement কথা দেয় মাঝে একটা করে space আর দুই মাথায় কোনো space নেই, তাই split(" ") হুবহু শব্দগুলোই দেয়, কোনো ফাঁকা টুকরো ছাড়া। words[j][0] হলো একটা শব্দের প্রথম অক্ষর, আর toUpperCase() ওর বড় হাতের রূপ নিয়ে একটা নতুন string return করে। প্রতিটা বড় হাতের অক্ষর আর ওর dot += দিয়ে জুড়ে জুড়ে লাইনটা তৈরি হয়।

আলাদা লাইনে word.toUpperCase() call করে তারপর word[0] ব্যবহার করলে print হয় ছোট হাতের অক্ষর, উপরের ভুল ৪ যেমন দেখায়। বড় হাতের অক্ষরগুলো join(".") দিয়ে জুড়লে শেষেরটার পরের dot বাদ পড়ে, print হয় A.L। charCodeAt(0) - 32 দিয়ে বড় হাতের অক্ষর বানানো শুধু ছোট হাতের অক্ষরে চলে, আর G-কে বানিয়ে দেয় একটা apostrophe।

Problem 5: slug-makerমাঝারিPro

Amara-র blog প্রতিটা post-এর web address বানায় ওর title থেকে: Hello, World! 2026 post-টা থাকে /hello-world-2026-এ। Slash-এর পরের অংশটাকে বলে slug। ওতে থাকে শুধু ছোট হাতের অক্ষর, অঙ্ক আর একটা একটা hyphen, তাই যেকোনো browser-এ ওটা পরিষ্কার পড়া যায়। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, মানে একটা title। n পড়ো nextInt() দিয়ে।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় একটা title।

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

Constraints. 1 <= n <= 10000। প্রতিটা title-এ 1 থেকে 200টা printable ASCII অক্ষর (code 32 থেকে 126)। Input-এর শেষ লাইনটা কখনো ফাঁকা না, আর প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

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"));
Run in Compiler

Hint 1

ছোট হাতের করার পরে কোন character-গুলো টিকে থাকে? বাদ পড়া তিনটা character পরপর থাকলে ওগুলো কী হওয়া উচিত?

Hint 2

Title-এর ভেতর দিয়ে একটা একটা character করে হাঁটো। অক্ষর বা অঙ্ক হলে রাখো। অন্য কিছু হলে মনে রাখো যে একটা hyphen পাওনা আছে, আর সেটা শোধ করো পরের রাখা character-এর ঠিক আগে, তবে slug-এ ততক্ষণে কিছু থাকলে তবেই।

Problem 6: csv-lineকঠিনPro

Kenji একটা contact list import করে, যেটা CSV-তে save করা: এমন একটা text format, যেখানে একটা record-এর field-গুলো কমা দিয়ে আলাদা। ওর প্রথম import প্রতিটা record-কে প্রতিটা কমায় কেটেছিল, আর address "12, Lake Road" হয়ে গিয়েছিল দুইটা column। Double quote-এর ভেতরের field-এ কমা থাকতে পারে, তাই import-কে quote-গুলো পড়তে জানতে হবে। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, ফাঁকা লাইনসহ। n পড়ো nextInt() দিয়ে।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় একটা record। একটা record-এর field-গুলো কমা দিয়ে আলাদা, আর প্রতিটা field হয় quote ছাড়া, নয়তো quote-এর ভেতরে। Quote ছাড়া field হলো কমা আর double quote ছাড়া অক্ষরের যেকোনো টানা সারি, ফাঁকা সারিও। Quote-এর ভেতরের field শুরু হয় একটা double quote দিয়ে, আর শেষ হয় সেই double quote-এ, যার ঠিক পরে একটা কমা বা record-এর শেষ। এই দুইয়ের মাঝে যেকোনো অক্ষর থাকতে পারে, কমাও, আর পরপর দুইটা double quote মানে একটা double quote। প্রতিটা record ঠিকঠাক নিয়ম মেনে লেখা। Space field-এরই অংশ, তাই কিছুই trim হয় না, আর একটা ফাঁকা record মানে একটা ফাঁকা field।

Output. n-টা লাইন, প্রতিটা record-এর জন্য একটা: field-এর সংখ্যা, তারপর প্রতিটা field-এর text square bracket-এর ভেতরে, সবগুলোর মাঝে একটা করে space। Quote-এর ভেতরের field print হয় বাইরের quote দুটো ছাড়া, আর ভেতরের প্রতিটা double quote-জোড়া হয় একটা quote।

Constraints. 1 <= n <= 10000। প্রতিটা record-এ 0 থেকে 500টা printable ASCII অক্ষর (code 32 থেকে 126)। Input-এর শেষ লাইনটা কখনো ফাঁকা না, আর প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

Sample. Input 3, তারপর Zara,14,Dhaka, "12, Lake Road",Amara,"She said ""hi""" আর ,, দিলে তিন লাইনে 3 [Zara] [14] [Dhaka], 3 [12, Lake Road] [Amara] [She said "hi"] আর 3 [] [] []।

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 records. Per record: the field count and every field in brackets.

console.log(out.join("\n"));
Run in Compiler

Hint 1

কমায় ভাগ করলে sample-এর প্রথম record ঠিক আসে। দ্বিতীয় record-এর কোন character ওটাকে ভুল করে দেয়? প্রতিটা কমায় সিদ্ধান্ত নিতে তোমার কী জানা দরকার?

Hint 2

Record-এর ভেতর দিয়ে একটা একটা character করে হাঁটো, সাথে একটা flag, যেটা বলে তুমি quote-এর ভেতরে আছ কি না। Quote-এর বাইরে একটা কমা field শেষ করে। ভেতরে পরপর দুইটা quote field-এ একটা quote যোগ করে, আর একা একটা quote field বন্ধ করে।

Problem 7: true-lengthমাঝারিPro

Maria-র badge printer প্রতিটা badge-এর মাপ ঠিক করত name.length দেখে, আর ওর বন্ধুর বাংলা নামের badge বেরোল ভুল মাপে। ঠিক করার আগে Maria প্রতিটা লাইনের জন্য দুটো হিসাব চায়: length কী বলে, আর লাইনটায় আসলে কয়টা code point। length গোনে UTF-16 code unit, মানে যে বাক্সগুলো দিয়ে একটা JavaScript string বানানো। Code point হলো একটা Unicode character-এর নম্বর। বেশিরভাগ character নেয় একটা code unit, কিন্তু U+FFFF-এর উপরের character, যেমন বেশিরভাগ emoji, নেয় দুইটা code unit, আর তবুও ওটা একটাই code point। এই problem starter-এর lines variant ব্যবহার করে: token-এ ভাগ করার জায়গায় বসে const lines = input.split("\n");, আর next() পড়ে lines থেকে, তাই ও দেয় পরের পুরো লাইনটা, ফাঁকা লাইনসহ। n পড়ো nextInt() দিয়ে।

Input. প্রথম লাইনে n, তারপর UTF-8-এ text-এর n-টা লাইন। একটা লাইন ফাঁকাও হতে পারে।

Output. n-টা লাইন, text-এর প্রতিটা লাইনের জন্য একটা: UTF-16 code unit-এ ওর দৈর্ঘ্য (length যা দেয়), একটা space, আর ওর code point-এর সংখ্যা।

Constraints. 1 <= n <= 10000। প্রতিটা লাইনে 0 থেকে 1000টা code point, কোনো carriage return নেই আর কোনো একা surrogate-ও নেই। Input-এর শেষ লাইনটা কখনো ফাঁকা না, আর প্রতিটা লাইন শেষ হয় একটা newline দিয়ে।

Sample. Input 3, তারপর Maria, মাহি 😀 আর 👋🏽 hi দিলে তিন লাইনে 5 5, 7 6 আর 7 5।

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: code units, then code points.

console.log(out.join("\n"));
Run in Compiler

Hint 1

length প্রথম সংখ্যাটা সরাসরিই দেয়। String-এর ভেতর দিয়ে code unit ধরে না হেঁটে code point ধরে হাঁটে কোনটা?

Hint 2

প্রতিটা লাইন পড়ো next() দিয়ে। Push করো ওর length, একটা space, আর লাইনটাকে code point-এ ছড়িয়ে দিলে যে array পাও তার মাপ।

Problem 8: anagram-checkমাঝারিPro

Kenji আর Bob একটা শব্দের খেলা খেলে। Kenji একটা শব্দ বলে, আর Bob-কে উত্তর দিতে হয় ওটার একটা anagram দিয়ে: এমন শব্দ, যেটা হুবহু একই অক্ষরগুলো ব্যবহার করে, প্রতিটা ঠিক ততবার। listen আর silent anagram। aab আর abb না, কারণ a একটায় আছে দুইবার, অন্যটায় একবার। Input পড়ো starter-এর nextInt() আর next() দিয়ে।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় দুইটা শব্দ a আর b, মাঝে একটা space। শব্দগুলো ছোট হাতের অক্ষর a থেকে z দিয়ে বানানো।

Output. n-টা লাইন, প্রতিটা জোড়ার জন্য একটা: b যদি হুবহু a-র অক্ষরগুলোই ব্যবহার করে, প্রতিটা ঠিক ততবার, তাহলে yes, নইলে no।

Constraints. 1 <= n <= 200000। প্রতিটা শব্দে 1 থেকে 100000টা অক্ষর, আর input-এর সব শব্দ মিলিয়ে সর্বোচ্চ 400000টা অক্ষর।

Sample. Input 4, তারপর listen silent, night thing, aab abb আর a a দিলে চার লাইনে yes, yes, no আর yes।

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 pairs of words. Per pair: yes or no.

console.log(out.join("\n"));
Run in Compiler

Hint 1

আলাদা দৈর্ঘ্যের দুইটা শব্দ কখনো anagram হয় না। একই দৈর্ঘ্যের দুইটার বেলায় বর্ণমালার প্রতিটা অক্ষর ধরে ধরে কী সমান হতে হবে?

Hint 2

Example 3-এর মতো 26টা গোনা রাখো। a-র প্রতিটা অক্ষরের জন্য এক যোগ করো, b-র প্রতিটা অক্ষরের জন্য এক বাদ দাও। শেষে প্রতিটা গোনা 0-তে ফিরলে শব্দ দুটো anagram।

Problem 9: first-uniqueমাঝারিPro

Alice-এর শব্দের খেলা প্রতিটা গোপন শব্দের জন্য একটা hint দেয়: শব্দের সেই প্রথম অক্ষর, যেটা ওতে ঠিক একবারই আছে, আর ওটা কোথায় বসে আছে। swiss-এর hint হলো index 1-এর w, কারণ s আছে তিনবার। Input পড়ো starter-এর nextInt() আর next() দিয়ে।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় একটা শব্দ। শব্দগুলো ছোট হাতের অক্ষর a থেকে z দিয়ে বানানো।

Output. n-টা লাইন, প্রতিটা শব্দের জন্য একটা: বাঁ দিক থেকে সেই প্রথম অক্ষর, যেটা শব্দে ঠিক একবার আছে, একটা space, আর 0 থেকে গুনে ওই অক্ষরের index। কোনো অক্ষরই ঠিক একবার না থাকলে print করো none।

Constraints. 1 <= n <= 400000। প্রতিটা শব্দে 1 থেকে 100000টা অক্ষর, আর input-এর সব শব্দ মিলিয়ে সর্বোচ্চ 400000টা অক্ষর।

Sample. Input 4, তারপর swiss, level, aabb আর z দিলে চার লাইনে w 1, v 2, none আর z 0।

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: the first letter seen once and its index, or none.

console.log(out.join("\n"));
Run in Compiler

Hint 1

পুরো শব্দ না দেখা পর্যন্ত তুমি জানতে পারো না একটা অক্ষর একবারই আছে কি না। তাহলে শব্দের ওপর দিয়ে কয়বার হাঁটতে হবে?

Hint 2

প্রথম pass 26টা বাক্সে প্রতিটা অক্ষর গোনে। দ্বিতীয় pass বাঁ দিক থেকে শব্দের ওপর দিয়ে হাঁটে, আর নেয় সেই প্রথম অক্ষর, যার গোনা 1।

Problem 10: run-lengthকঠিনPro

Kenji ওর game-এর level file-গুলো ছোট করতে চায়, যেগুলো aaaa-র মতো লম্বা সারিতে ভরা। Run-length encoding প্রতিটা সারিকে লেখে ওর অক্ষর আর ওর দৈর্ঘ্য দিয়ে, তাই aaabcc হয়ে যায় a3b1c2। লাভ হয় শুধু তখনই, যখন ফলাফল ছোট হয়, তাই নইলে Kenji শব্দটা যেমন আছে তেমনই রাখে। Input পড়ো starter-এর nextInt() আর next() দিয়ে।

Input. প্রথম লাইনে n, তারপর n-টা লাইন, প্রতিটায় একটা শব্দ। শব্দগুলো ASCII অক্ষর a থেকে z আর A থেকে Z দিয়ে বানানো।

Output. n-টা লাইন, প্রতিটা শব্দের জন্য একটা। শব্দটাকে সারিতে (run) ভাগ করো: সারি মানে একই অক্ষরের টানা পুনরাবৃত্তি, যতদূর যায়, আর বড় হাতের অক্ষর আর ওর ছোট হাতের রূপ আলাদা অক্ষর। প্রতিটা সারি লেখো ওর অক্ষর, তারপর দশমিকে ওর দৈর্ঘ্য দিয়ে। এই রূপটা শব্দের চেয়ে ছোট হলে সেটা print করো, নইলে শব্দটাই। দৈর্ঘ্য সমান হলেও শব্দটাই থাকে।

Constraints. 1 <= n <= 400000। প্রতিটা শব্দে 1 থেকে 100000টা অক্ষর, আর input-এর সব শব্দ মিলিয়ে সর্বোচ্চ 400000টা অক্ষর।

Sample. Input 4, তারপর aaabccdddd, abc, aabb আর zzzzzzzzzzzz দিলে চার লাইনে a3b1c2d4, abc, aabb আর z12।

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: the run-length form if shorter, else the word.

console.log(out.join("\n"));
Run in Compiler

Hint 1

একটা সারি কোথায় শেষ হয়? প্রতিটা অক্ষরকে মেলাও সেই অক্ষরের সাথে, যেটা দিয়ে চলতি সারিটা শুরু হয়েছিল।

Hint 2

চলতি সারি কোন index-এ শুরু হলো, মনে রাখো। অক্ষর বদলালে, বা শব্দ শেষ হলে, সারির অক্ষর আর দৈর্ঘ্য packed রূপে জুড়ে দাও, আর নতুন সারি শুরু করো। একদম শেষে দুটো দৈর্ঘ্য মেলাও।

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

  • ছয়টা problem-এ lines variant কেন লাগে? শব্দগুলো তো token হিসেবেই পড়তে পারতাম।

    Token দুটো জিনিস হারায়: ফাঁকা লাইন আর space। word-count-কে ফাঁকা লাইনের জন্য 0 গুনতে হয়, caesar-shift-কে প্রতিটা space রাখতে হয়, আর csv-line field-এর ভেতরের space রাখে। Token দিয়ে তো এটাও বোঝা যায় না, এক লাইন কোথায় শেষ আর পরেরটা কোথায় শুরু।

  • আমার lines array-তে input-এর লাইনের চেয়ে একটা element বেশি। ওটা কি সরাতে হবে?

    না। Input শেষ হয় একটা newline দিয়ে, তাই তার পরের text একটা ফাঁকা string, আর split("\n") ওটাকে শেষ element হিসেবে রেখে দেয়। তোমার loop n-টা লাইন পড়ে ওটার আগেই থামে। Statement কথা দেয় শেষ আসল লাইনটা কখনো ফাঁকা না, তাই দুটো কখনো গুলিয়ে যায় না।

  • slug-maker-এ কি regular expression ব্যবহার করতে পারি?

    Judge শুধু তোমার output দেখে, তাই ঠিক regular expression পাশ করে যায়। ওগুলো শেখাবে Module 16; ততদিন পাওনা hyphen-ওয়ালা loop তোমাকে সেই ভাবনাটা শেখায়, যেটা একটা pattern ঢেকে রাখে। আসল code-এ regular expression প্রায়ই বেশি পরিষ্কার, আর কখন, সেটা বলে lesson 04।

  • true-length কেন code point গোনে, পাঠক যে character দেখে সেগুলো না?

    Code unit আর code point নির্দিষ্ট সংখ্যা, প্রতিটা engine-ই একমত। পাঠক কোনটাকে একটা character দেখে, সেটা চলে Unicode-এর segmentation-এর নিয়মে, যেগুলো প্রতিটা Unicode version-এ বাড়ে। ওটা গোনে Intl.Segmenter, আর কীভাবে, সেটা দেখায় lesson 05।

  • আমার program sample পাশ করে। তাহলে hidden test কেন ফেল করে?

    Sample একটাই ছোট উদাহরণ, statement বোঝানোর জন্য বাছা। Hidden test-এ আরও থাকে ফাঁকা লাইন, পরপর কয়েকটা space, negative shift, emoji, quote-এর ভেতরের কমা আর সবচেয়ে বড় মাপ। উপরের কিনারার table ওদের নাম বলে দেয়; submit করার আগে ছোটগুলো Playground-এ চালিয়ে দেখো।

মূল কথা

  • আলাদা আলাদা শব্দ পড়ো token starter দিয়ে, আর যে text-এ space বা ফাঁকা লাইনের দাম আছে সেটা lines variant দিয়ে।
  • split(" ") space-গুলোর মাঝের ফাঁকা টুকরো রেখে দেয়; .filter(Boolean) ওগুলো ফেলে দেয়।
  • অক্ষর আসলে সংখ্যা: charCodeAt আর String.fromCharCode দুইয়ের মাঝে আসা-যাওয়া করে, আর ((k % 26) + 26) % 26 যেকোনো shift-কে ঘুরিয়ে আনে।
  • String কখনো নিজের জায়গায় বদলায় না: toUpperCase() আর বাকি প্রতিটা method যা return করে, সেটাই ব্যবহার করো।
  • length গোনে code unit, [...s].length গোনে code point, আর পাঠক যা দেখে সেটা এদের কেউই গোনে না।
  • আরও গভীরে: CP and Interview Pack, string-এর pattern, প্রশ্ন আর bug gallery (Pro)।

এরপর আসছে module test: দশটা প্রশ্ন আর এই set-এর দুটো problem, palindromes আর caesar-shift। তারপর cheat sheet পুরো module-কে এক পাতায় আনে, আর Module 4 খোলে if, switch আর loop।

lesson ৭ শেষ

সব problem accepted হলেই lesson শেষ।

৩ টা free problem-এর মধ্যে ০ টা accepted

পরেরটা: Cheat sheet: এক পাতায় string

Problem: string | Learn JavaScript | Progsity