@jakeybakers

Dialing Letters Instead of Digits

Overlapping lists of random words and numbers

In February 2023, I unleashed the first iteration of Cifra, my website that generates and displays as many vanity phone numbers that match an inputted phone number as fast as possible. Over the past year and a half, I've found different ways to improve the efficiency and design of the program. It required a good understanding of how vanity phone numbers work and an ample amount of time to remove the inappropriate words from the word list that I found online. Even though the code may seem simple, I started with an extremely slow and inefficient method before finding this much faster one, which I'll explain here.

Generation Preparation

The first step in this process is to make the list of over 40,000 words more manageable. For this specific case, we need to have two separate lists/arrays: one containing the words (which we already have), and another containing the translation of each word to how you would type it in the telephone number keypad.

The program will convert each letter of each word to the digits on the keypad that they correspond to. In the program, these strings of numbers that correspond to a word* are called "numeros." Each numero is added to an array called numerosArray.

*More than one word can share the same numero, but this won't affect the program.

numerosArray = [];

for (j = 0; j < wordsArray.length; j++) {
    numero = "";
    for (k = 0; k < wordsArray[j].length; k++) {
        numeroLetter = getNumber(wordsArray[j][k]);
        numero += numeroLetter;
    }
    numerosArray.push(numero);
}

The getNumber() function takes a letter and returns the number that it corresponds to on the phone keypad.

Below is the widely used standard telephone keypad. A word like "call" would be converted to its numero letter by letter: C to 2, A to 2, L to 5, and L to 5, resulting in a numero of 2255.

Image by Marnanel - Own work based on: Telephone-keypad.svg by Silsor, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=395327

The standard telephone keypad

Below is an example list of words and their corresponding numeros after running the code.

A visual of how Cifra converts its word list to an array of "numeros"

Match and Form

Now that the word list is in a more manageable state, let's start forming the vanity numbers. This is the slightly more complicated part.

Step 1 (one-word vanity numbers)

First, the program will loop through each numero and check if the inputted phone number contains any of the numeros. If it does, it will replace that numero in the inputted phone number with the corresponding word. Then, it will add that vanity number to an array called oneNumsArray.

for (m = 0; m < numerosArray.length; m++) {
    if (inp.includes(numerosArray[m])) {
        finalNumb = inp.replace(numerosArray[m], "-" + wordsArray[m] + "-");
        oneNumsArray.push(fixDashes(finalNumb));
    }
}

The fixDashes() function removes any duplicate dashes or any dashes that are at the very beginning or end of the vanity number so that there is only one dash between each position where a letter and a digit meet.

Below is an example of this step. 230-286-6911 is the inputted phone number, and while looping through numerosArray, it finds that the phone number contains a numero, 28669. It replaces the numero in the phone number with the word that the numero corresponds to, which is "bunny". It adds this new vanity phone number to oneNumsArray.

A visual of how Cifra matches a vanity number to a numero, and then puts in the corresponding word

After it finishes looping through each numero, oneNumsArray will be filled with every possible one-word vanity number that can be derived from the inputted phone number.

Step 2 (two-word vanity numbers)

We don't just want vanity numbers with one word in them when we could have more, but repeating this process could slow down the program by at least several tens of milliseconds and at most several hundred, which equates to several years from a programmer's perspective.

So, before the process is repeated, the program should do a check to see if it's worth it to sacrifice a little more time to attempt to generate more vanity numbers.

  1. One of the first things to check is if there have even been any one-word vanity numbers generated at all from the previous step.
  2. Another less obvious thing to check is the digits themselves. On the standard telephone keypad, the numbers 0 and 1 don't correspond to any letters, so if those are the only digits left on all of the previously generated one-word vanity numbers, there's no point in trying to find another match with them, since there are none that exist. For example, it is impossible to squeeze any more words out of the one-word vanity number 10-PLANT-010, since there are no corresponding letters for any of the remaining digits.
  3. Additionally, the shortest word in the program's word list is 3 letters (apart from a few exceptions).

Using these important things to know, I added a statement to check the first and last one-word vanity numbers generated for their longest strings of consecutive digits from 2 to 9. If that string in either one of them has a length greater than 2 (at least 3) and there is at least one one-word vanity number generated from before, then the program will repeat the same process from the previous step, but now on the list of one-word vanity numbers.

if (oneNumsArray.length > 0 && (numOfGoodNums(oneNumsArray[oneNumsArray.length - 1]) > 2 || numOfGoodNums(oneNumsArray[0]) > 2))

It will loop through each vanity number in oneNumsArray and each numero to check if any of the one-word vanity numbers contain any of the numeros. If it does, it will replace that numero in the one-word vanity number with the corresponding word. Then, it will add this new two-word vanity number to an array called twoNumsArray.

for (n = 0; n < oneNumsArray.length; n++) {
    for (o = 0; o < numerosArray.length; o++) {
        if (oneNumsArray[n].includes(numerosArray[o])) {
            finalNumb = oneNumsArray[n].replace(numerosArray[o], "-" + wordsArray[o] + "-");
            twoNumsArray.push(fixDashes(finalNumb));
        }
    }
}

Step 3 (three-word vanity numbers)

The same checks and processes will be repeated one last time to add a potential third word to the vanity numbers.

if (twoNumsArray.length > 0 && (numOfGoodNums(twoNumsArray[twoNumsArray.length - 1]) > 2 || numOfGoodNums(twoNumsArray[0]) > 2))
for (p = 0; p < twoNumsArray.length; p++) {
    for (q = 0; q < numerosArray.length; q++) {
        if (twoNumsArray[p].includes(numerosArray[q])) {
            finalNumb = twoNumsArray[p].replace(numerosArray[q], '-' + wordsArray[q] + '-');
            threeNumsArray.push(fixDashes(finalNumb));
        }
    }
}

Reveal the Numbers

After all of this, we now have three arrays of vanity numbers: one array that has numbers with one word, another that has numbers with two words, and the last with three words. The program will combine all the arrays into one and remove any duplicate vanity numbers. It will then display every vanity number in a pseudo-random order, generally showing the vanity numbers with more, longer words first.

Most ten-digit phone numbers generate all their possible vanity numbers in about 30 to 200 milliseconds on average. Is there a more efficient way to do this? Almost certainly, but for me, this is fast enough.

GitHub

You can find the full code for Cifra on GitHub.