A numeronym (also known as a "numerical contraction") is when a word is shortened using numbers. A common contraction method is to use the length of the replaced substring to replace all but the first and last characters. For example, use i18n instead of internationalization, or L10n instead of localization. (The L is capitalized since a lowercase one looks too similar to the 1.)

Of course, several words in the same phrase may have the same abbreviated form, so your job is to convert a group of words to their numeronyms, or in the event that there exists some different words with the same numeronym, your program should give a result of A7s R4t, short for Ambiguous Result (yes, I know that this result is itself an ambiguous result.)


  • Use a program or function, and print or return the result.
  • Input is taken as a single string.
  • The output is a single string of space-delimited words.
  • Your program need not convert words with length 3, and should not convert shorter words.
  • If an l (lowercase ell) would occur before a 1 (one), it should be made uppercase.
  • If an I (uppercase eye) would occur before a 1 (one), it should be made lowercase.
  • Input will be printable ASCII and spaces. Words are separated by spaces.
  • Shortest code wins.


A7s R4t -> A7s R4t (OR: A1s R1t, etc)
Ambiguous Result -> A7s R4t
Billy goats gruff -> B3y g3s g3f
Thanks for the Memories -> T4s f1r the M6s (one possible answer, NOT: Thnks fr th Mmrs)
Programming Puzzles & Code Golf -> P9g P5s & C2e G2f
globalization -> g11n
localizability -> L12y
Internationalization or antidisestablishmentarianism -> i18n or a26m
Internationalization or InternXXXXXalization -> A7s R4t
what is this fiddle and faddle -> A7s R4t
A be see -> A be s1e (OR: A be see)
see sea -> s1e s1a (OR: see sea)
2B or not 2B -> 2B or not 2B (OR: 2B or n1t 2B. 2 letters, don't change, don't count as ambiguous)
this example is this example -> t2s e5e is t2s e5e (same words aren't ambiguous)
l1 -> l1 (2 letters, don't change.)
I1 -> I1 (2 letters, don't change.)

Edit: If anyone didn't get the reference: Thnks fr th Mmrs

  • \$\begingroup\$ Can the input contain numbers as well? If so, does an input of l1 become L1 or stay as l1? \$\endgroup\$
    – Doorknob
    Commented Dec 4, 2015 at 22:01
  • \$\begingroup\$ I presume from the emphasis on different that example example would be e5e e5e, but it would be good to include a test case which covers this. \$\endgroup\$ Commented Dec 4, 2015 at 22:12
  • \$\begingroup\$ Added test cases for both of your questions (2B or not 2B and after.) \$\endgroup\$
    – mbomb007
    Commented Dec 4, 2015 at 22:17

9 Answers 9


Caché ObjectScript, 231 bytes

r(a,b) s o=$REPLACE(o,a,b) q
z(s) f i=1:1:$L(s," ") s u=$P(s," ",i),l=$L(u),o=$S(l<4:u,1:$E(u)_(l-2)_$E(u,l)) d:l>3 r("I1","i1"),r("l1","L1") d  g:r z+4
    . i '(l<4!(v(o)=u!'$D(v(o)))) s r=1 q
    . s v(o)=u,t=t_o_" "
    q t
    q "A7s R4t"

This would be good-ol' standards-compliant MUMPS if it weren't for that pesky $REPLACE call, which isn't part of the standard. Reimplementing it in pure M takes a good 80ish bytes, so I didn't go down that route.

The entry point is $$z("your string here"), which returns "y2r s4g h2e", and so forth.

  • 3
    \$\begingroup\$ Could you perhaps link to the language? I haven't heard of it. \$\endgroup\$
    – mbomb007
    Commented Dec 4, 2015 at 23:03
  • \$\begingroup\$ I mean, it's the thing that comes up when you google for it (docs.intersystems.com/cache20152/csp/docbook/…) - a proprietary flavor of MUMPS. \$\endgroup\$
    – senshin
    Commented Dec 5, 2015 at 0:47

C#, 280 274 bytes

First time golfer here! Been enjoying reading these lately and so I thought I might try out some myself! Probably not the best solution, but oh well!

class B{static void Main(string[] a){string[] n=Console.ReadLine().Split(' ');string o="";int j,i=j=0;for(;j<n.Length;j++){int s=n[j].Length;n[j]=((s<4)?n[j]:""+n[j][0]+(s-2)+n[j][s-1])+" ";o+=n[j];for(;i<j;i++)if(n[j]==n[i]){o="A7s R4t";j=n.Length;}}Console.WriteLine(o);}}

Same thing ungolfed:

class B
    static void Main(string[] a)
        string[] n = Console.ReadLine().Split(' ');
        string o = "";
        int j, i = j = 0;
        for(; j < n.Length;j++)
            int s = n[j].Length;

            n[j] = ((s<4) ? n[j] : "" + n[j][0] + (s - 2) + n[j][s - 1]) + " ";
            o += n[j];
            for (; i < j; i++)
                if (n[j] == n[i]) { o = "A7s R4t"; j=n.Length;}

Thanks guys!

  • \$\begingroup\$ Welcome to PPCG! c: \$\endgroup\$
    – Deusovi
    Commented Mar 28, 2016 at 16:57

Perl, 131 120 bytes

I've added a byte for using the -p switch:

s/\B(\w+)(\w)/length($1)."$2_$1"/ge;$_="A7s R4t\n"if/(\w\d+\w)(\w+)\b.*\1(?!\2)/;s/_\w+//g;s/I1\w/\l$&/g;s/l1\w/\u$&/g;


# Replace internal letters with count, but keep them around for the next test.
# Detect ambiguous result
$_ = "A7s R4t\n" if
    # Use negative look-ahead assertion to find conflicts
# We're done with the internal letters now
# Transform case of initial 'I' and 'l', but only before '1'
  • \$\begingroup\$ Not sure whether is correct but I expected the “l” in “like” to not get capitalized when transformed into “l2e”, as is not followed by “1”. \$\endgroup\$
    – manatwork
    Commented Dec 9, 2015 at 12:43
  • \$\begingroup\$ Tested just a couple of cases, but seem to work: s/(\w)(\w+)(\w)/$1.length($2)."$3_$2"/ges/\B(\w+)(\w)/length($1)."$2_$1"/ge. \$\endgroup\$
    – manatwork
    Commented Dec 9, 2015 at 12:52
  • \$\begingroup\$ Thanks, @manatwork. I overlooked the requirement to caseswap [Il] only before 1 - saved a couple of bytes, too! \$\endgroup\$ Commented Dec 9, 2015 at 13:15

J, 92 bytes

(' 'joinstring(<&(]`(rplc&('I1';'i1')&(rplc&('l1';'L1'))&({.,([:":#-2:),{:))@.(#>3:))&>&;:))

A looong chain of composed calls. The first part is just a fork: a literal, joinstring, and a function (in J, 1 (10 + +) 2 is (10 + (1 + 2))). The second part is the function. The two rplc calls are to replace the I/l when they could be confused. & is composition, and composing an operator with a value curries. (so 3&+ gives a function that expects the 2nd argument to be added). Finally, the last part is the function that takes the first, the length-2 (composed with stringification), and the last. the &>&;: bit at the end composes this function (which should be applied to every argument) to > (unboxing), because ;: (split words) returns a boxed list (so that every element can have different length).


     (' 'joinstring(<&(]`(rplc&('I1';'i1')&(rplc&('l1';'L1'))&({.,([:":#-2:),{:))@.(#>3:))&>&;:)) 'hey you baby Iooooooooneey I1'
hey you b2y i11y I1

JavaScript (ES6), 165 bytes

s=>(r=s.replace(/\S+/g,w=>(m=w.length-2)<2?w:(i=d.indexOf(n=((m+s)[0]<2&&{I:"i",l:"L"}[w[0]]||w[0])+m+w[m+1]))>=0&d[i+1]!=w?v=0:d.push(n,w)&&n,v=d=[]),v?r:"A7s R4t")


s=>(                         // s = input string
  r=s.replace(               // r = result of s with words replaced by numeronyms
    /\S+/g,                  // match each word
    w=>                      // w = matched word
      (m=w.length-2)         // m = number of characters in the middle of the word
        <2?w:                // if the word length is less than 4 leave it as it is
      (i=d.indexOf(          // check if this numeronym has been used before
        n=                   // n = numeronymified word
          ((m+s)[0]<2&&      // if the number starts with 1 we may need to replace
            {I:"i",l:"L"}    //     the first character with one of these
              [w[0]]||w[0])+ // return the replaced or original character
      ))>=0&d[i+1]!=w?       // set result as invalid if the same numeronym has been
        v=0:                 //     used before with a different word
      d.push(n,w)&&n,        // if everything is fine return n and add it to the list
    v=                       // v = true if result is valid
      d=[]                   // d = array of numeronyms used followed by their original word
  v?r:"A7s R4t"              // return the result


<input type="text" id="input" value="lnternationalization or antidisestablishmentarianism" />
<button onclick='result.textContent=(

s=>(r=s.replace(/\S+/g,w=>(m=w.length-2)<2?w:(i=d.indexOf(n=((m+s)[0]<2&&{I:"i",l:"L"}[w[0]]||w[0])+m+w[m+1]))>=0&d[i+1]!=w?v=0:d.push(n,w)&&n,v=d=[]),v?r:"A7s R4t")

<pre id="result"></pre>


JavaScript ES6, 162

w=>(v=(w=w.split` `).map(x=>(l=x.length-2+'')>1?((l[0]>1||{I:'i',l:'L'})[x[0]]||x[0])+l+x[-~l]:x)).some((a,i)=>v.some((b,j)=>a==b&w[i]!=w[j]))?'A7s R4t':v.join` `

Less golfed

// Less golfed
  w = w.split` ` // original text splitted in words
  v = w.map(x=> { // build modified words in array v

    l = x.length - 2 // word length - 2
    if (l > 1) // if word length is 4 or more
      a = x[0] // get first char of word
      l = l+'' // convert to string to get the first digit
      m = l[0] > 1 || {I:'i', l:'L'} // only if first digit is 1, prepare to remap I to i and l to L
      a = m[a] || a // remap
      return a + l + x[-~l] // note: -~ convert back to number and add 1
      return x // word unchanged
  return v.some((a,i)=>v.some((b,j)=>a==b&w[i]!=w[j])) // look for equals Numeronyms on different words
  ? 'A7s R4t' 
  : v.join` `


F=w=>(v=(w=w.split` `).map(x=>(l=x.length-2+'')>1?((l[0]>1||{I:'i',l:'L'})[x[0]]||x[0])+l+x[-~l]:x)).some((a,i)=>v.some((b,j)=>a==b&w[i]!=w[j]))?'A7s R4t':v.join` `


;['A7s R4t','Ambiguous Result','Billy goats gruff','Thanks for the Memories',
'Programming Puzzles & Code Golf','globalization','localizability',
'Internationalization or antidisestablishmentarianism',  
'Internationalization or InternXXXXXalization','what is this fiddle and faddle',
'A be see','see sea','2B or not 2B','this example is this example','l1','I1']
.forEach(t=>console.log(t+' -> '+F(t)))
<pre id=O></pre>


Python 2, 185 bytes

for w in input().split():
 if l<4:x=w
 if d.get(x,w)!=w:r='A7s R4t';break
 d[x]=w;r+=x+' '
print r.strip()

Python 3, 160

I'd love to find a good way to replace those replace calls.

def f(a):y=[(x,(x[0]+str(len(x)-2)+x[-1]).replace('l1','L1').replace('I1','i1'))[len(x)>3]for x in a.split()];return('A7s R4t',' '.join(y))[len(set(y))==len(y)]

With some test cases:

assert f('Billy goats gruff') == 'B3y g3s g3f'
assert f('Programming Puzzles & Code Golf') == 'P9g P5s & C2e G2f'
assert f('Internationalization or InternXXXXXalization') == 'A7s R4t'

Factor, 48 35 bytes, noncompeting

It's a lambda that goes on the stack, that doesn't technically meet the really annoyingly hard requirements that I originally managed to overlook.

[ " " split [ a10n ] map " " join ]

Uses the english vocabulary.

Or, if we inline the a10n library word, then 131 bytes (with auto-imports):

: a ( b -- c ) " " split [ dup length 3 > [ [ 1 head ] [ length 2 - number>string ] [ 1 tail* ] tri 3append ] when ] map " " join ;
  • \$\begingroup\$ Does this handle the i/L stuff and A7s R4t? \$\endgroup\$ Commented Mar 28, 2016 at 4:10
  • \$\begingroup\$ @RobertFraser I'm certain it does, though I'm not at a Factor compiler this minute; I'll add passing unit tests when I am. :D \$\endgroup\$
    – cat
    Commented Mar 28, 2016 at 10:43
  • \$\begingroup\$ Yeah, sorry about the requirements. A week after I finished the question, I was like "oh no, what have I done," but it was too late. I might make a simpler version as a separate challenge. \$\endgroup\$
    – mbomb007
    Commented Apr 13, 2016 at 21:07

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.