Last week, we looked at iterators and generators in JavaScript. We learned that we can change an object's default iteration behaviour by adding an [@@iterator]() method to the object. We also learned that generator functions make this easier. What we didn't cover is that generator functions don't have to be implemented as [@@iterator]() methods. Let's create some generator functions to help us generate the letters of the alphabet!

Declaring a generator function

To declare a generator function, we append an asterisk (*) to the function keyword. We can do this using a function declaration or function expression. The main difference is that function declarations are hoisted while function expressions are not. We cannot declare a generator function using an arrow function expression.

// Function declaration
function* gen() {}

// Function expression
let gen = function* () {};

We're going to declare two generator functions: genAlphaUpper() and genAlphaLower().

Generating the uppercase letters

Let's declare our genAlphaUpper() generator function. It will have two parameters: start with a default value of 0 and stop with a default value of 26. The first parameter refers the index of the first letter to be included in the sequence; the second parameter refers to the index of the first letter to be excluded from the sequence. This is for parity with the String.prototype.slice() method. It means we can compute the number of generated letters as stop - start.

function* genAlphaUpper(start = 0, stop = 26) {}

We'll declare two constants: ALPHABET_LENGTH with a value of 26 and LATIN_CAPITAL_LETTER_A with a value of 65. The former is the number of letters in the alphabet while the latter is the code point for the uppercase letter "A". In the Unicode Standard, this is actually represented as a hexadecimal (base-16) number with a U+ prefix: U+0041. For simplicity, we'll use the equivalent decimal (base-10) number: 65. If we wanted to be consistent with the Unicode Standard, we could use the hexadecimal number 0x0041 instead (hexadecimal numbers are written with a 0x prefix in JavaScript).

function* genAlphaUpper(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_CAPITAL_LETTER_A = 65;
}

It would be nice if we could pass in negative indices to count backwards from 26. We'll check if the values of the start and stop parameters are less than 0. If so, we'll add the value of the ALPHABET_LENGTH constant (26) to them. For example, if we passed in a start value of -1, this would be computed as -1 + 26, which is equal to 25. This is the index of the final letter in the alphabet.

function* genAlphaUpper(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_CAPITAL_LETTER_A = 65;

if (start < 0) {
start += ALPHABET_LENGTH;
}

if (stop < 0) {
stop += ALPHABET_LENGTH;
}
}

Once we've computed the indices, we need to make sure they're in range. If the value of the start parameter is less than 0 or the value of the stop parameter is greater than the value of the ALPHABET_LENGTH constant (26), we'll throw a RangeError.

function* genAlphaUpper(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_CAPITAL_LETTER_A = 65;

if (start < 0) {
start += ALPHABET_LENGTH;
}

if (stop < 0) {
stop += ALPHABET_LENGTH;
}

if (start < 0 || stop > ALPHABET_LENGTH) {
throw new RangeError("Indices are out of range.");
}
}

Now we'll add the value of the LATIN_CAPITAL_LETTER_A constant (65) to the values of the start and stop parameters. In the Unicode Standard, the code points for the uppercase letters "A" to "Z" are U+0041 (65) to U+005A (90). Adding 65 takes us to the code points in this range.

function* genAlphaUpper(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_CAPITAL_LETTER_A = 65;

if (start < 0) {
start += ALPHABET_LENGTH;
}

if (stop < 0) {
stop += ALPHABET_LENGTH;
}

if (start < 0 || stop > ALPHABET_LENGTH) {
throw new RangeError("Indices are out of range.");
}

// In the Unicode Standard, the code points for the uppercase letters A to Z
// are the hexadecimal numbers U+0041 (65) to U+005A (90).
start += LATIN_CAPITAL_LETTER_A;
stop += LATIN_CAPITAL_LETTER_A;
}

Finally, we'll use a simple for loop to yield the return value of the static String.fromCodePoint() method on each invocation of the generator function.

function* genAlphaUpper(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_CAPITAL_LETTER_A = 65;

if (start < 0) {
start += ALPHABET_LENGTH;
}

if (stop < 0) {
stop += ALPHABET_LENGTH;
}

if (start < 0 || stop > ALPHABET_LENGTH) {
throw new RangeError("Indices are out of range.");
}

// In the Unicode Standard, the code points for the uppercase letters A to Z
// are the hexadecimal numbers U+0041 (65) to U+005A (90).
start += LATIN_CAPITAL_LETTER_A;
stop += LATIN_CAPITAL_LETTER_A;

for (let i = start; i < stop; i++) {
yield String.fromCodePoint(i);
}
}

Generating the lowercase letters

Our genAlphaLower() function will be almost identical, except we need to use the code points U+0061 (97) to U+007A (122). In the Unicode Standard, these represent the lowercase letters "a" to "z". We'll use the constant LATIN_SMALL_LETTER_A instead of LATIN_CAPITAL_LETTER_A.

function* genAlphaLower(start = 0, stop = 26) {
const ALPHABET_LENGTH = 26;
const LATIN_SMALL_LETTER_A = 97;

if (start < 0) {
start += ALPHABET_LENGTH;
}

if (stop < 0) {
stop += ALPHABET_LENGTH;
}

if (start < 0 || stop > ALPHABET_LENGTH) {
throw new RangeError("Indices are out of range.");
}

// In the Unicode Standard, the code points for the lowercase letters a to z
// are the hexadecimal numbers U+0061 (97) to U+007A (122).
start += LATIN_SMALL_LETTER_A;
stop += LATIN_SMALL_LETTER_A;

for (let i = start; i < stop; i++) {
yield String.fromCodePoint(i);
}
}

Using the generator functions

Generator functions return Generator objects. These are a type of iterable iterator. Because of this, we can use the returned Generator object with syntaxes that expect an iterable. This includes spread syntax (...) and the for...of statement.

Creating an array

To create an array of the uppercase letters "A" to "Z", we can use spread syntax to iterate over the returned Generator object. Note that we could also use the Array.from() method.

let alphabet = [...genAlphaUpper()];

Looping through the values

To loop through the uppercase letters "A" to "Z", we can use a for...of loop to iterate over the returned Generator object.

for (let letter of genAlphaUpper()) {
console.log(letter);
}

Generating a subset

Because our generator functions accept start and stop indices, we can generate a subset of the alphabet. This is more memory-efficient than generating the entire alphabet and slicing it.

let eToG = [...genAlphaUpper(4, 7)];

If the indices don't make sense, e.g. if we try to start at index 12 and stop at index 9, the generator functions won't yield anything. This is because the conditions of the for loops will never be truthy, so the for loops will never execute.

Working with the Generator object

For more control, we can work with the Generator object directly. For example, we can invoke the Generator.prototype.next() method manually.

let gen = genAlphaUpper();

let done = false;

while (!done) {
let result = gen.next();

if (typeof result.value !== "undefined") {
console.log(result.value);
}

done = result.done;
}

Summary

In this post, we declared two generator functions: genAlphaUpper() and genAlphaLower(). Respectively, they use the static String.fromCodePoint() method to generate the alphabet in uppercase and lowercase. Because they accept start and stop indices, they can generate subsets of the alphabet instead of forcing us to generate the entire alphabet and slice it. This is more memory-efficient.