Learn JavaScript

Lesson 7 of 9 · Strings and Template Literals

Module 3 · Strings and Template Literals

Problems: Strings

FreeProblems

In this lesson

  • Pick the right reader for each problem: the token starter for single words, and the lines variant for text with spaces or empty lines.
  • Walk a string one character at a time with charCodeAt, String.fromCharCode, a count array or a flag, and build the answer line with += or a template literal.
  • Test the hidden tests' edges before you submit: an empty line, a run of spaces, a negative shift, an emoji and a quoted comma.

Ten problems, graded against hidden tests on Node 22, the same JavaScript the Playground runs. You met every one of them as an exercise inside this module's lessons. Here they stand together, from the word counter of lesson 02 to the run-length form of lesson 06.

Problems 1, 2 and 4 (word-count, palindromes and initials) are free for everyone. The other seven are Pro problems: locks per problem arrive in a later release, and until then all ten are open. The module test has two problems, palindromes and caesar-shift, so both are worth solving here first.

Bob reads the sample, writes the first program that prints it, and submits. Zara first feeds her program an empty line, three spaces in a row and an emoji. The hidden tests in this set are built from exactly those, so Zara's habit is the one that earns the marks.

The token starter or the lines variant

The starter you know splits the whole input into tokens. A token is a piece of text between spaces or line breaks, so a token never holds a space. That suits four problems here, whose input is single words: palindromes, anagram-check, first-unique and run-length.

The other six read text in which a space matters or a line may be empty. The token split throws both away. .filter(Boolean) drops every empty string, so an empty line vanishes, and the spaces are gone before your code sees them. So these six use the lines variant. Two lines of the starter change: const lines = input.split("\n"); replaces the token split, and next() reads from lines.

Now next() returns one whole line, spaces and all, and an empty line arrives as "". nextInt() still works for the line that holds n, because Number() reads a line holding only a number. The diagram follows the sample of word-count through the lines variant.

The word-count sample, read with the lines variant One input, read line by line stdin: one string lines: an array words per line 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 "" after the last \n 4 words 0 words 1 word 4 words split(" ").filter(Boolean) A dot · marks a space. nextInt() turns box 0 into the number 4, and each next() after it returns one whole box: spaces kept, "" for an empty line. The loop reads 4 lines, so box 5, the text after the final newline, is never read. The token split would give 10 tokens here, and no line would be empty any more.

The array has one box more than the input has lines, because the text after the last newline is an empty string. Your loop reads exactly n lines and never touches it. That is also why every lines-variant statement promises a last line that is never empty. No answer then depends on how a trailing empty line is read.

The judge compares text, so every character counts

The judge runs your whole program on a hidden input and compares what it prints, line by line. Spaces at the end of a line are ignored, and so are empty lines at the very end. Everything else must match exactly. A capital where the statement has a small letter, a missing dot or a space at the start of a line is a wrong answer.

Every answer in this set is text you build yourself, so the statement is your only guide. yes is not Yes, A.L. is not A. L., and a slug with a hyphen at the end is a different slug. Build each line, push it into out, and let the starter's one console.log print them all.

The edges the hidden tests try

Every problem has 11 to 16 hidden tests. They run from the smallest input to the largest the constraints allow, and each test file stays under 1 MiB. Between those two sits every string this module taught you to distrust. The table names them, so you can try them first.

ProblemAccessTestsWhat the hidden tests try
word-countFree13empty lines, runs of spaces at the start, middle and end, a line of 300 spaces, one-letter words, punctuation glued to words, 20000 lines, 19999 empty lines in a row
palindromesFree12one-letter and two-letter words, mixed capitals such as rAcEcAr, palindromes broken only in their innermost pair, 20000 words of 50 letters
caesar-shiftPro16k = 0, 13, 25, 26, 52, -1, -13, -26, -27, 1000 and -1000, a text with no letters, the six characters between Z and a, 100000 characters
initialsFree11one-letter words, six words of 20 letters, names in all capitals and in mixed case, 20000 names
slug-makerPro12titles with no letter or digit, the underscore, runs at both ends, the characters next to a to z and 0 to 9 in the code table, titles of 200 characters, 10000 titles
csv-linePro14an empty record, a record of 500 commas, a quoted empty field "", """" for one quote, spaces around fields, brackets inside fields, records of 500 characters
true-lengthPro14empty lines, both spellings of the Bangla য়, a family emoji joined by ZWJ, a flag, a skin tone, combining accents, lines of only ASCII, lines of 1000 emoji and other characters above U+FFFF
anagram-checkPro12words of different lengths, pairs with the same sum of character codes, two words of 100000 letters, 200000 pairs of one letter
first-uniquePro12no unique letter, a unique letter at the end, several unique letters, words of 100000 letters, 200000 words of two letters
run-lengthPro13AAaa and other capitals beside small letters, forms of equal length, runs of 9, 10, 99, 100, 999 and 1000, a run of 100000, 300000 words of one letter

Try the small edges on the Playground. Its stdin box takes at most 10000 characters, so it cannot hold the largest tests. That limit belongs to the box, not to the judge. A test you write yourself, with one edge in it, says more than a big one anyway.

The forms these ten problems need

const line = next();               lines variant: one whole line, "" when empty
const word = next();               token starter: one word, never a space
line.split(" ").filter(Boolean)    the words of a line, runs of spaces skipped
s.toLowerCase()                    a new string in small letters; s is unchanged
s[0].toUpperCase()                 the first character, as a capital
s.charCodeAt(i)                    the code of unit i: "A" is 65, "a" is 97
String.fromCharCode(code)          the one-character string for a code
a % b                              keeps the sign of a: -1 % 26 is -1
s.length                           code units; [...s].length counts code points
counts[s.charCodeAt(i) - 97]++     one box per letter, "a" to "z"
`${letter}${count}`                a template literal: values into one string
  • Every starter below is the fixed starter of the track, or its lines variant where the problem says so. Keep its lines, and write your code where the comment says.
  • A plain for loop and if are enough for every problem. You met them in Module 1, lesson 05, and Module 4 teaches them.
  • Push one line of output per answer, exactly as the statement writes it.
Example 1: the same input, as tokens and as lines

Zara feeds a program four lines: the number 3, two words with two spaces between them, an empty line, and two words after a space. The program runs the lines variant and also splits the same input into tokens, so the two readers sit side by side.

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

That output is for the input 3, then Zara tests, an empty line and edge cases with one space before it, ending with a newline as every judge test does. The brackets show each line exactly. Line 1 keeps its two spaces, line 2 is the empty line, and line 3 keeps its leading space.

The five tokens are 3, Zara, tests, edge and cases: the empty line and every space are gone. Box 4 is the empty string after the final newline, the box no loop of n lines ever reads. Typed into the Playground's stdin box with no Enter after the last line, the input has no final newline, and box 4 is not there.

Run in Compiler
Example 2: letters are numbers, and Z + 1 is not A

Alice wants to see the codes behind her Caesar notes. The program reads one word and prints each character, its code and the character one code further on.

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 |

That output is for the input AZaz@[`{. The capitals run from 65 to 90 and the small letters from 97 to 122, the ASCII table of lesson 02. One code past Z is [, not A, so a shift has to wrap by itself.

The six codes from 91 to 96 sit between Z and a. A letter test written as ch >= "A" && ch <= "z" lets all six in, and caesar-shift has a hidden test for exactly that.

Run in Compiler
Example 3: twenty-six boxes, one per letter

Kenji counts the letters of each word with an array of 26 numbers, one box per letter. charCodeAt(j) - 97 picks the box: 0 for a, 25 for z. The program prints every letter whose box is not 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

That output is for the input 2 and banana kenji. The letters come out in alphabet order, because the program walks the boxes, not the word. One pass fills the boxes, whatever the word's length.

Two problems are built on these boxes. anagram-check fills them from one word and empties them with the other. first-unique fills them first, then walks the word itself, because it wants the order of the word, not of the alphabet.

Run in Compiler

Where this is used

  • GNU wc -w. The word counter that ships with Linux counts a word as a run of characters between white space, the rule of word-count. Run on the sample's four lines of text, it prints 9, the sum of 4, 0, 1 and 4.
  • RFC 4180, the CSV standard. A field that holds a comma, a double quote or a line break should be written in double quotes. A quote inside it is written twice. Python's csv module reads "12, Lake Road",Amara as two fields, the same as csv-line.
  • ROT13 in Python. ROT13 is the Caesar shift by 13, used to hide spoilers. Python's codecs module ships it as a codec: codecs.encode("Hello, Zara!", "rot13") gives Uryyb, Mnen!.

Common mistakes

1. Counting the pieces of split(" ") as words.

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

That is the output for the input 3, then Amara writes every day, an empty line and hello. The right counts are 4, 0 and 1. split(" ") cuts at every single space, so two spaces in a row leave an empty piece between them, and an empty line splits into [""], one piece. You will make this mistake because every test you type by hand has single spaces. Add .filter(Boolean).

2. % on a 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

No error, and a shifted back by one prints a backtick instead of z. In JavaScript, % keeps the sign of the number on its left, so -1 % 26 is -1, not 25. You will make this mistake because every forward shift works. ((k % 26) + 26) % 26 turns any k into a forward shift from 0 to 25.

3. Splitting a CSV record on every comma.

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

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

The record has two fields, and split(",") finds three, with a quote stuck to each half of the address. split knows only its separator and nothing about quotes. You will make this mistake because most test records have no quotes at all. Walk the record with a flag that says whether you are inside quotes.

4. Calling toUpperCase() and keeping the old string.

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

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

Line 2 makes "ADA" and throws it away, because a string never changes in place: every method returns a new string. So word is still "ada", and the first initial prints in small letters. You will make this mistake because the call reads like an order to the word. Use the value the method returns.

Brain teaser

Amara publishes three posts on the same day. Their titles are C++ Tips, C# Tips and C Tips!.

Without running anything: what slug does slug-maker give each title? What goes wrong when all three go live on Amara's blog, and what would you change to fix it?

Write each title in small letters, then mark every character that is not a to z or 0 to 9. A slug is part of a web address.

Problem 1: word-countEasyFree

Amara's blog editor shows a word count under every paragraph. Bob's first version counted 5 words in a line with a double space, and 1 word in an empty line. Amara wants a counter that is right on every line, however the spaces fall. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, empty lines included. Read n with nextInt().

Input. The first line holds n, and n lines of text follow. A word is a maximal run of characters that are not spaces. A line may be empty, may start or end with spaces, and may hold several spaces in a row.

Output. n lines: the number of words on each line of text, in the input order.

Constraints. 1 <= n <= 20000. Each line of text has 0 to 300 printable ASCII characters (codes 32 to 126), and the last one is never empty. Every line ends with a newline.

Sample. Input 4, then Amara writes every day (two spaces after writes), an empty line, hello (two spaces before it) and one two three four, gives 4, 0, 1 and 4 on four lines.

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

What does "a b".split(" ") give? Count its pieces before you count words.

Hint 2

Read each line with next(). Split it on a single space, drop the empty pieces, and push how many pieces are left.

Solution

line.split(" ") cuts at every space. Two spaces in a row leave an empty string between them, and a space at either end leaves one there too. .filter(Boolean) keeps only the non-empty pieces, which are exactly the words. An empty line splits into [""], which filters down to nothing, so it counts 0.

Without the filter, the sample's first line counts 5 and the empty line counts 1, as mistake 1 above shows. The token starter loses the line breaks, so nothing tells you where one line's words end. The hidden tests add a line of 300 spaces, punctuation glued to words and 19999 empty lines in a row.

Problem 2: palindromesEasyFree

Zara collects palindromes, words that read the same backwards, like noon. Her list mixes capitals and small letters, and she wants Level to count: to a reader, L and l are the same letter. Read the input with the starter's nextInt() and next().

Input. The first line holds n. The second line holds n words, separated by single spaces. Each word is made of the ASCII letters a to z and A to Z.

Output. n lines, one per word, in the input order: yes when the word reads the same backwards with capitals and small letters treated alike, else no.

Constraints. 1 <= n <= 20000. Each word has 1 to 50 letters.

Sample. Input 5 and Level noon Kenji Anna ab gives yes, yes, no, yes and no on five lines.

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 and level must get the same answer. Which one call makes them the same word?

Hint 2

Lowercase the word once. Then compare it with its own reverse, or walk two indexes inward from both ends and compare the letters they point at.

Solution

After toLowerCase(), a palindrome is a word equal to its own reverse. word.split("").reverse().join("") builds the reverse: the letters as an array, the array turned around, the letters joined again. The words here are ASCII letters, so split("") is safe; lesson 04 shows why it is not safe for an emoji.

The loop from both ends does the same without building a new string: compare word[i] with word[word.length - 1 - i] while i is in the first half. Bob compares with word[word.length - i], which is undefined on the first step, so he says no to every word. Comparing without toLowerCase() says no to Level, and the sample catches it.

Problem 3: caesar-shiftMediumPro

Alice and Zara pass notes in a Caesar code: every letter moves k places along the alphabet. The alphabet wraps around, so z is followed by a. Zara picks the shift, and she likes to pick it negative, or far past 26, to see what breaks. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, spaces included. Read k with nextInt().

Input. The first line holds the whole number k. The second line holds the text.

Output. One line: the text with every letter moved k places forward in the alphabet, or backward when k is negative. The move wraps around, from z to a going forward and from a to z going back. A capital stays a capital and a small letter stays small. Every other character, a space included, is unchanged.

Constraints. -1000 <= k <= 1000. The text has 1 to 100000 printable ASCII characters (codes 32 to 126). The last line of the input is never empty, and every line ends with a newline.

Sample. Input 3 and Hello, Zara! xyz gives 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

Number the small letters from 0 to 25. A shift adds k and wraps around 26. What does -1 % 26 give in JavaScript, and what should it give?

Hint 2

Turn k into a forward shift from 0 to 25 once, before the loop. Then test each character's code against the range of capitals and the range of small letters. Move a letter within its own range, and keep every other character as it is.

Problem 4: initialsEasyFree

David prints name badges for a coding camp, and every badge carries the person's initials in one corner. People type their names in any mix of capitals: ada lovelace, ZARA, kEnJi. The badge always shows capitals. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, one full name. Read n with nextInt().

Input. The first line holds n, and n lines follow, one full name each. A name is 1 to 6 words of ASCII letters, separated by single spaces, with no space at either end.

Output. n lines, one per name: the first letter of every word as a capital, each followed by a dot, with nothing between them.

Constraints. 1 <= n <= 20000. Each word has 1 to 20 letters, in any mix of capitals and small letters. The last line of the input is never empty, and every line ends with a newline.

Sample. Input 3, then ada lovelace, Grace Brewster Murray Hopper and zara, gives A.L., G.B.M.H. and Z. on three lines.

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 has four words on one line. How do you get the words of a line, when they are separated by single spaces?

Hint 2

Split the line on a space. For every word, take its first character, make it a capital, and add it and a dot to the line you are building.

Solution

The statement promises single spaces and no space at either end, so split(" ") gives exactly the words, with no empty pieces. words[j][0] is a word's first letter, and toUpperCase() returns a new string holding its capital. Adding each capital and its dot with += builds the line.

Calling word.toUpperCase() on its own line and then using word[0] prints a small letter, as mistake 4 above shows. Joining the capitals with join(".") drops the dot after the last one and prints A.L. Making a capital with charCodeAt(0) - 32 works for small letters only, and turns G into an apostrophe.

Problem 5: slug-makerMediumPro

Amara's blog builds the web address of every post from its title: the post Hello, World! 2026 lives at /hello-world-2026. The part after the slash is called the slug. It holds only small letters, digits and single hyphens, so it reads well in any browser. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, one title. Read n with nextInt().

Input. The first line holds n, and n lines follow, one title each.

Output. n lines, one slug per title. To make a slug, write the title in small letters. Then turn every maximal run of characters that are not a to z or 0 to 9 into one hyphen. Remove a hyphen at the start or at the end. If nothing is left, print the word untitled.

Constraints. 1 <= n <= 10000. Each title has 1 to 200 printable ASCII characters (codes 32 to 126). The last line of the input is never empty, and every line ends with a newline.

Sample. Input 3, then Hello, World! 2026, --- Strings & Template Literals --- and !!!, gives hello-world-2026, strings-template-literals and untitled on three lines.

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

After lowercasing, which characters survive? What should a run of three dropped characters in a row become?

Hint 2

Walk the title one character at a time. Keep a letter or a digit. For anything else, remember that a hyphen is owed, and pay it just before the next kept character, but only once the slug holds something.

Problem 6: csv-lineHardPro

Kenji imports a contact list saved as CSV, a text format where commas separate the fields of a record. His first import split every record on its commas and turned the address "12, Lake Road" into two columns. A field in double quotes may hold commas, so the import has to read the quotes. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, empty lines included. Read n with nextInt().

Input. The first line holds n, and n lines follow, one record each. The fields of a record are separated by commas, and each field is unquoted or quoted. An unquoted field is any run of characters with no comma and no double quote, the empty run included. A quoted field starts with a double quote. It ends with the double quote that is followed by a comma or by the end of the record. Between the two, any character may appear, a comma included, and two double quotes in a row stand for one. Every record is well formed. Spaces belong to the field, so nothing is trimmed, and an empty record is one empty field.

Output. n lines, one per record: the number of fields, then the text of every field in square brackets, all separated by single spaces. A quoted field is printed without its outer quotes, and each pair of double quotes inside it as one.

Constraints. 1 <= n <= 10000. Each record has 0 to 500 printable ASCII characters (codes 32 to 126). The last line of the input is never empty, and every line ends with a newline.

Sample. Input 3, then Zara,14,Dhaka, "12, Lake Road",Amara,"She said ""hi""" and ,,, gives 3 [Zara] [14] [Dhaka], 3 [12, Lake Road] [Amara] [She said "hi"] and 3 [] [] [] on three lines.

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

Splitting on commas gets the sample's first record right. Which character of the second record makes it wrong? At each comma, what must you know to decide?

Hint 2

Walk the record one character at a time with a flag that says whether you are inside quotes. Outside quotes, a comma ends a field. Inside, two quotes in a row add one quote to the field, and a single quote closes it.

Problem 7: true-lengthMediumPro

Maria's badge printer sized every badge by name.length, and her friend's Bangla name came out the wrong size. Before she fixes it, Maria wants two counts for every line: what length says, and how many code points the line holds. length counts UTF-16 code units, the boxes a JavaScript string is made of. A code point is one Unicode character number. Most characters take one code unit, but a character above U+FFFF, like most emoji, takes two code units and is still one code point. This problem uses the lines variant of the starter: const lines = input.split("\n"); replaces the token split. Then next() reads from lines and returns the next whole line, empty lines included. Read n with nextInt().

Input. The first line holds n, and n lines of text follow, in UTF-8. A line may be empty.

Output. n lines, one per line of text: its length in UTF-16 code units (what length gives), a space, and its number of code points.

Constraints. 1 <= n <= 10000. Each line has 0 to 1000 code points, no carriage return and no lone surrogate. The last line of the input is never empty, and every line ends with a newline.

Sample. Input 3, then Maria, মাহি 😀 and 👋🏽 hi, gives 5 5, 7 6 and 7 5 on three lines.

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 gives the first number as it is. What walks a string by code point instead of by code unit?

Hint 2

Read each line with next(). Push its length, a space, and the size of the array you get by spreading the line into its code points.

Problem 8: anagram-checkMediumPro

Kenji and Bob play a word game. Kenji says a word, and Bob must answer with an anagram of it: a word that uses exactly the same letters, each as many times. listen and silent are anagrams. aab and abb are not, because a appears twice in one and once in the other. Read the input with the starter's nextInt() and next().

Input. The first line holds n, and n lines follow, each with two words a and b, separated by a space. The words are made of the small letters a to z.

Output. n lines, one per pair: yes when b uses exactly the letters of a, each as many times, else no.

Constraints. 1 <= n <= 200000. Each word has 1 to 100000 letters, and all the words of the input together have at most 400000 letters.

Sample. Input 4, then listen silent, night thing, aab abb and a a, gives yes, yes, no and yes on four lines.

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

Two words of different lengths are never anagrams. For two of the same length, what must be equal, letter by letter of the alphabet?

Hint 2

Keep 26 counts, as in Example 3. Add one for each letter of a and take one away for each letter of b. The words are anagrams when every count ends at 0.

Problem 9: first-uniqueMediumPro

Alice's word game gives one hint per secret word: the first letter of the word that appears in it exactly once, and where it sits. For swiss the hint is w at index 1, because s appears three times. Read the input with the starter's nextInt() and next().

Input. The first line holds n, and n lines follow, one word each. The words are made of the small letters a to z.

Output. n lines, one per word. Print the first letter, from the left, that appears exactly once in the word, then a space and its index counting from 0. When no letter appears exactly once, print none.

Constraints. 1 <= n <= 400000. Each word has 1 to 100000 letters, and all the words of the input together have at most 400000 letters.

Sample. Input 4, then swiss, level, aabb and z, gives w 1, v 2, none and z 0 on four lines.

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

You cannot know that a letter appears once until you have seen the whole word. So how many passes over the word do you need?

Hint 2

The first pass counts every letter in 26 boxes. The second pass walks the word from the left and takes the first letter whose count is 1.

Problem 10: run-lengthHardPro

Kenji wants to shrink the level files of his game, which are full of long runs like aaaa. Run-length encoding writes each run as its letter and its length, so aaabcc becomes a3b1c2. It only helps when the result is shorter, so Kenji keeps the word as it is otherwise. Read the input with the starter's nextInt() and next().

Input. The first line holds n, and n lines follow, one word each. The words are made of the ASCII letters a to z and A to Z.

Output. n lines, one per word. Split the word into runs: a run is a maximal stretch of one letter repeated, and a capital and its small letter are different letters. Write every run as its letter followed by its length in decimal. Print that form when it is strictly shorter than the word, else the word itself.

Constraints. 1 <= n <= 400000. Each word has 1 to 100000 letters, and all the words of the input together have at most 400000 letters.

Sample. Input 4, then aaabccdddd, abc, aabb and zzzzzzzzzzzz, gives a3b1c2d4, abc, aabb and z12 on four lines.

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

Where does a run end? Compare each letter with the letter the current run started with.

Hint 2

Remember the index where the current run starts. When the letter changes, or the word ends, add the run's letter and its length to the packed form and start a new run. Compare the two lengths at the very end.

Common doubts

  • Why do six problems need the lines variant? I could read the words as tokens.

    Tokens lose two things: the empty lines and the spaces. word-count has to count 0 for an empty line, caesar-shift has to keep every space, and csv-line keeps spaces inside its fields. With tokens you cannot even tell where one line ends and the next begins.

  • My lines array has one more element than the input has lines. Do I have to remove it?

    No. The input ends with a newline, so the text after it is an empty string, and split("\n") keeps it as the last element. Your loop reads n lines and stops before it. The statements promise the last real line is never empty, so the two can never be confused.

  • Can I use a regular expression in slug-maker?

    The judge sees only your output, so a correct regular expression passes. Module 16 teaches them; until then, the loop with an owed hyphen teaches you the thinking a pattern hides. In real code, the regular expression is often the clearer choice, and lesson 04 says when.

  • Why does true-length count code points, not the characters a reader sees?

    Code units and code points are fixed numbers that every engine agrees on. What a reader sees as one character follows Unicode's segmentation rules, which grow with each Unicode version. Intl.Segmenter counts it, and lesson 05 shows how.

  • My program passes the sample. Why does a hidden test fail?

    The sample is one small case, chosen to explain the statement. The hidden tests add empty lines, runs of spaces, negative shifts, emoji, quoted commas and the largest sizes. The table of edges above names them; run the small ones on the Playground before you submit.

Key takeaways

  • Read single words with the token starter, and any text where a space or an empty line matters with the lines variant.
  • split(" ") keeps the empty pieces between spaces; .filter(Boolean) drops them.
  • A letter is a number: charCodeAt and String.fromCharCode move between the two, and ((k % 26) + 26) % 26 wraps any shift.
  • A string never changes in place: use what toUpperCase() and every other method returns.
  • length counts code units, [...s].length counts code points, and neither counts what a reader sees.
  • Go deeper: CP and Interview Pack, string patterns, the questions and the bug gallery (Pro).

Next comes the module test: ten questions and two of these problems, palindromes and caesar-shift. After it, the cheat sheet puts the whole module on one page, and Module 4 opens if, switch and loops.

End of lesson 7

Get every problem accepted, and the lesson is done.

0 of 3 free problems accepted

Next: Cheat Sheet: Strings on One Page