The major system is a mnemonic device for converting numbers into words so they can be memorized more easily.

It is based on how words sound phonetically, but to keep things simple for the challenge we'll only be concerned with how words are spelled. This means there will be some incorrect conversions, but that's alright.

To convert a number into a word using our simplified major system:

  • Replace each 0 with s or z. (Some could be s and some could be z. Same goes below.)
  • Replace each 1 with t or d or th.
  • Replace each 2 with n.
  • Replace each 3 with m.
  • Replace each 4 with r.
  • Replace each 5 with l.
  • Replace each 6 with j or sh or ch.
  • Replace each 7 with k or c or g or q.
  • Replace each 8 with f or v.
  • Replace each 9 with p or b.
  • Add the letters aehiouwxy anywhere in any amounts to make a real English word, if possible.
    The only exception is that h may not be inserted after an s or c.

The number may actually be any string of the digits 0-9 (no decimals or commas or signs).
The word can only contain the lowercase letters a-z.


The number 32 must be converted as ?m?n?, where ? represents any finite string made from the letters aehiouwxy (a string from the free monoid if you prefer). There are many ways this could be made into a real English word: mane, moon, yeoman, etc.

The number 05 could be converted as ?s?l? or ?z?l?. Some possibilities are easily, hassle, and hazel. The word shawl is not allowed because h may not be placed after s; it would be incorrectly read as 65.


Write a program or function that takes in a string of the digits 0-9 and finds all the words that it could be converted into using the simplified major system mnemonic.

Your program has access to a word list text file that defines what all the "real" English words are. There is one lowercase a-z word on each line of this file, and you may optionally assume it has a trailing newline. Here is a list of real words you can use for testing. You can assume this word list file is called f (or something longer) and lies in any convenient directory.

For a 35 byte penalty (add 35 to your score) you may assume the word list is already loaded into a variable as a list of strings. This is mainly for languages that can't read files, but any submission may take advantage of it.

Your program must output all the words in the word list that the input number can be converted to. They should be printed to stdout (or similar), one per line (with an optional trailing newline), or they can be returned as a list of strings if you chose to write a function. The word list is not necessarily alphabetized and the output doesn't need to be either.

If there are no possible words then the output (or the list) will be empty. The output is also empty if the empty string is input.

Take input via stdin, command line, or as a string argument to a function. The word list, or its file name, should not be part of the input, only the digit string.

You are only matching single words in the word list, not sequences of words. The word noon would probably be one of the results for 22, but the word sequence no one wouldn't.

Test Cases

Suppose this is the word list:


The input 0123456789 should give all the long words except zdnmrlshchvb and shthnmrlchgvb:


The input 99 should give:


(The output words may be in any order.)


The shortest submission in bytes wins. Tiebreaker goes to the submission posted first.

Nifty related site: numzi.com.

  • 1
    \$\begingroup\$ Did you happen to get the idea for this challenge from this video? Because I actually just watched that yesterday. :P \$\endgroup\$
    – Doorknob
    Feb 23, 2015 at 12:54
  • 1
    \$\begingroup\$ @Doorknob Not that video, but that guy. Years ago I was given one of his Great Courses lectures. He's kinda zany but does really neat stuff. :) \$\endgroup\$ Feb 23, 2015 at 12:57
  • 2
    \$\begingroup\$ Note for those interested in using the mnemonic major system in real life: it is only the sound that matters, not the spelling. So "c", while listed here as meaning 7, could actually mean 0 if in the word it is pronounced with an "s" sound (as in "ace" = 0). However, I'm sure that the OP simplified the challenge, as a dictionary with full phonetics is much harder to come by than a simple word list. Oh, and one rendering of 23940 is "numbers". \$\endgroup\$
    – ErikE
    Feb 23, 2015 at 18:38
  • 1
    \$\begingroup\$ @ErikE I state that we're using a spelling based version in the second sentence of the post... \$\endgroup\$ Feb 23, 2015 at 22:58
  • \$\begingroup\$ I see that now, though I did miss it at first--but it still seems to me that your explanation could be fleshed out just a little bit more and an example or two provided. \$\endgroup\$
    – ErikE
    Feb 24, 2015 at 0:15

6 Answers 6


Perl, 87 84

open A,f;"@ARGV"eq s/[cs]h/j/gr=~y/stnmrljkfpzdcgqvb\0-z/0-90177789/dr&&print for<A>

Takes input in as the command line parameter:

$perl m.pl 23940

Can be made somewhat shorter if the word list would be allowed on the standard input:

$perl -lnE'INIT{$;=pop}$;eq s/[cs]h/j/gr=~y/stnmrljkfpzdcgqvba-z/0-90177789/dr&&say' 99 <f
  • \$\begingroup\$ What does A mean in open A,f? \$\endgroup\$
    – feersum
    Feb 23, 2015 at 15:15
  • \$\begingroup\$ @feersum A file handle used later to read the file (<A>). \$\endgroup\$
    – nutki
    Feb 23, 2015 at 15:16

Python 2, 215 208 bytes

This Python solution builds a regex from parts indexed by the command line argument, then tests each word with that (rather large) regex.

import re,sys
a='[sz] (d|th?) n m r l (j|sh|ch) [kcgq] [fv] [pb]'.split()
for i in sys.argv[1]:b+=a[int(i)]+z
for d in open('f'):
 if re.match('^'+b+'$',d):print d

Original source before the minifier:

import re,sys
regexbits = '[sz] (d|th?) n m r l (j|sh|ch) [kcgq] [fv] [pb]'.split()

regex = other = '((?<![sc])h|[aeiouwxy])*'
for i in sys.argv[1] :
    regex += regexbits[int(i)] + other
print regex     # DEBUG

for word in open('f'):
    word = word.strip()
    if re.match('^'+regex+'$', word) :
        print word

For example, the regex for the test 99 is:


The (?<![sc])h bit is a "look behind negative assertion" component that makes sure a h does not follow a s or c in the general filler parts.

Thanks Calvin. This challenge motivated me to brush up on my rusty regex skills.

  • \$\begingroup\$ b=c='((?<![sc])h|[aeiouwxy])*' will save two bytes. \$\endgroup\$
    – matsjoyce
    Feb 23, 2015 at 20:39
  • \$\begingroup\$ t|th -> th? saves a byte \$\endgroup\$
    – Sp3000
    Feb 23, 2015 at 21:35
  • \$\begingroup\$ You can avoid the map by taking int(i) directly. \$\endgroup\$
    – xnor
    Feb 23, 2015 at 21:44
  • \$\begingroup\$ Thanks matsjoyce, Sp3000, and xnor for useful golfing tips. Now edited with suggestions implemented. \$\endgroup\$ Feb 24, 2015 at 3:34

Python 3, 170

import sys,re
for s in open('f'):re.sub('sh|ch','j',s).translate(t)!=sys.argv[1] or print(s,end='')

Readable version:

import sys, re

table = str.maketrans('sztdnmrljkcgqfvpb', '00112345677778899', 'aehiouwxy\n')

for line in open('f'):
    line = re.sub('sh|ch', 'j', line)
    if line.translate(table) == sys.argv[1]:
        print(line, end='')

The code makes use of the fact that th is redundant (since it maps to the same number as t, and h is a padding character).

The static maketrans function creates a table mapping the characters of the first argument to those of the second argument, and the characters of the third argument to None (which will result in those characters being deleted).

The final code could be made a few bytes shorter by creating the table as a direct argument of translate.

  • \$\begingroup\$ You could save a couple of bytes using input() instead of sys.argv[1] and '[sc]h' for your regular expression. \$\endgroup\$
    – swstephe
    Feb 25, 2015 at 21:21
  • \$\begingroup\$ @swstephe. Thanks for the feedback, but I don't think input() can be used, because it is called inside a loop. Also, your suggested regex is the same length as the one I'm already using (5 bytes). \$\endgroup\$
    – ekhumoro
    Feb 25, 2015 at 21:31
  • \$\begingroup\$ I was thinking something like "z,t=input(),str.maketrans...", then just use z instead of sys.argv. Okay, I thought my regex was 4-bytes. \$\endgroup\$
    – swstephe
    Feb 25, 2015 at 21:58

sed,paste,grep,cut - 109

sed -e 's/[sc]h/6/g;s/[aehiouwxy]//g;y/sztdnmrljkcqgfvpb/00112345677778899/' w|paste w -|grep " $1$"|cut -f1

Takes a file "w", converts each word to its number, paste back to the original, grep for the number and return the word matched. Note that the whitespace after the quote after grep is a tab, paste's default delimiter.

I know Perl is way ahead, just wanted a better shell version as an example.

Oh yeah, the $1 part means that this is supposed to be run from a shell script, (most shells should work), so it takes a command-line argument.

  • \$\begingroup\$ I was thinking about converting my answer to pure sed to avoid Perl's open and @ARGV overhead, but the lack of ranges and delete functions in y/// breaks it. Surprisingly though even though there are no variables you can express the logic itself directly in sed. Here is my 92 solution: sed -e'h;s/[sc]h/6/g;y/sztdnmrljkcqgfvpb/00112345677778899/;s/[^0-9]*//g;T;s/^$1$//;x;t;d' f \$\endgroup\$
    – nutki
    Feb 26, 2015 at 8:48
  • \$\begingroup\$ It seems to work, why not make it an answer? \$\endgroup\$
    – swstephe
    Feb 26, 2015 at 19:15

Bash + coreutils, 216

sed -n "$(sed 's/[aeiouwxy]//g
tl'<w|grep -nf <(eval printf '%s\\n' `sed 's/0/{s,z}/g
s/9/{p,b}/g'<<<$1`)|sed s/:.\*/p/)" w
  • The word list in a file called w
  • The innermost sed replaces digits with their possible replacements
  • The eval printf uses shell brace expansions to expand out all the possible substitutions
  • The second sed on the 1st line removes aeiouwxy and h (when not preceded by [sc]) from the word list
  • The grep prints out all matches, with line numbers
  • Since we have stripped out aeiouwxy and h from the word list, the last sed turns the results of grep (line numbers of each match) to another sed expression, which is processed by the outermost sed to reveal all possible words from the wordlist.


The word list file is specified as a command-line arg, followed by the number to mnemonicize:

ubuntu@ubuntu:~$ ./numzi.sh 99
$ ./numzi.sh 0123456789
  • \$\begingroup\$ @Calvin'sHobbies Done. \$\endgroup\$ Feb 23, 2015 at 23:51
  • \$\begingroup\$ Looks like you forgot to update your example. \$\endgroup\$ Feb 24, 2015 at 0:10

tr, sed, grep, xargs, sh, 77

tr 0123456789 ztnmrljkfp|sed 's/ */[aehiouwxy]*/g'|xargs sh -c 'grep -x $0 f'

Expects the number in stdin and the word list should be stored in the file f.

Does not uses all replacements (1 will always be z, 7 will always be k), so it may be called a lazy solution, but it finds at least one mnemonic for 95 numbers in [1-100].

  • 3
    \$\begingroup\$ The question asks that you find all matching words in the word list. You can't make 1 always be z or 7 always k. This in invalid. \$\endgroup\$ Feb 23, 2015 at 23:03
  • \$\begingroup\$ Fair enough, I'll remove my answer. \$\endgroup\$
    – pgy
    Feb 26, 2015 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.