views:

4063

answers:

29

The puzzle

A little puzzle I heard while I was in high school went something like this...

  • The questioner would ask me to give him a number;
  • On hearing the number, the questioner would do some sort of transformation on it repeatedly (for example, he might say ten is three) until eventually arriving at the number 4 (at which point he would finish with four is magic).
  • Any number seems to be transformable into four eventually, no matter what.

The goal was to try to figure out the transformation function and then be able to reliably proctor this puzzle yourself.

The solution

The transformation function at any step was to

  • Take the number in question,
  • Count the number of letters in its English word representation, ignoring a hyphen or spaces or "and" (e.g., "ten" has 3 letters in it, "thirty-four" has 10 letters in it, "one hundred forty-three" has 20 letters in it).
  • Return that number of letters.

For all of the numbers I have ever cared to test, this converges to 4. Since "four" also has four letters in it, there would be an infinite loop here; instead it is merely referred to as magic by convention to end the sequence.

The challenge

Your challenge is to create a piece of code that will read a number from the user and then print lines showing the transformation function being repeatedly applied until "four is magic" is reached.

Specifically:

  1. Solutions must be complete programs in and of themselves. They cannot merely be functions which take in a number-- factor in the input.
  2. Input must be read from standard input. (Piping from "echo" or using input redirection is fine since that also goes from stdin)
  3. The input should be in numeric form.
  4. For every application of the transformation function, a line should be printed: a is b., where a and b are numeric forms of the numbers in the transformation.
  5. Full stops (periods) ARE required!
  6. The last line should naturally say, 4 is magic..
  7. The code should produce correct output for all numbers from 0 to 99.

Examples:

> 4
4 is magic.

> 12
12 is 6.
6 is 3.
3 is 5.
5 is 4.
4 is magic.

> 42
42 is 8.
8 is 5.
5 is 4.
4 is magic.

> 0
0 is 4.
4 is magic.

> 99
99 is 10.
10 is 3.
3 is 5.
5 is 4.
4 is magic.

The winner is the shortest submission by source code character count which is also correct.

BONUS

You may also try to write a version of the code which prints out the ENGLISH NAMES for the numbers with each application of the transformation function. The original input is still numeric, but the output lines should have the word form of the number.

(Double bonus for drawing shapes with your code)

(EDIT) Some clarifications:

  1. I do want the word to appear on both sides in all applicable cases, e.g. Nine is four. Four is magic.
  2. I don't care about capitalization, though. And I don't care how you separate the word tokens, though they should be separated: ninety-nine is okay, ninety nine is okay, ninetynine is not okay.

I'm considering these a separate category for bonus competition with regard to the challenge, so if you go for this, don't worry about your code being longer than the numeric version.

Feel free to submit one solution for each version.

+10  A: 

J, 107 112 characters

'4 is magic.',~}:('.',~":@{.,' is ',":@{:)"1]2&{.\.
(]{&(#.100 4$,#:3 u:ucp'䌵䐵吶梇禈榛ꪛ멩鮪鮺墊馊꥘誙誩墊馊ꥺ겻곋榛ꪛ멩鮪鮺'))^:a:

(Newline for readability only)

Usage and output:

    '4 is magic.',~}:('.',~":@{.,' is ',":@{:)"1]2&{.\.(]{&(#.100 4$,#:3 u:ucp'䌵䐵吶梇禈榛ꪛ멩鮪鮺墊馊꥘誙誩墊馊ꥺ겻곋榛ꪛ멩鮪鮺'))^:a:12
12 is 6.    
6 is 3.     
3 is 5.     
5 is 4.     
4 is magic. 
David
Where is the "4 is magic" bit? What about the "is" in the middle?
Josh K
Need to print a full stop as well.
Adam
It's compiled in chinese
belisarius
Yep, still working on that.
David
And, it now conforms to the spec.
David
Okay, we need a referee call: are we defining "characters" as 'bytes stored" or "Unicode code points"?
Mike DeSimone
Please choose a non-chinese referee
belisarius
@beli: 멩, 겻, 곋, 멩 are Korean.
KennyTM
I have to admit, I'd like to know what the byte scheme is so I know why this works... It feels vaguely unfair not to use literal bytes in this case.
Platinum Azure
@KennyTM Spies are everywhere these days
belisarius
My wife (a native Chinese speaker) says it's a mix of Chinese and Korean.
Loren Pechtel
@Loren Pechtel A translation could be wonderful!
belisarius
@belisarius: 1) She doesn't know Korean. 2) The Chinese is gibberish.
Loren Pechtel
@Loren Pechtel That may become a great idea for restricting the use of ucp in golfing ... valid answers should have correct spelling ... in chinese :D
belisarius
+1  A: 

Python:

#!/usr/bin/env python

# Number of letters in each part, we don't count spaces
Decades = ( 0, 3, 6, 6, 6, 5, 5, 7, 6, 6, 0 )
Smalls  = ( 0, 3, 3, 5, 4, 4, 3, 5, 5, 4 )
Teens  =  ( 6, 6, 8, 8, 7, 7, 9, 8, 8 )

def Count(n):
    if n > 10 and n < 20: return Teens[n-11]
    return   Smalls[n % 10 ] + Decades [ n / 10 ]

N = input()

while N-4:
    Cnt = Count(N)
    print "%d is %d" % ( N, Cnt)
    N = Cnt

print "4 is magic"
Vlad
I like it. You could probably tighten it up a bit though.
Josh K
@Vlad: Input should be read from stdin instead from the arguments. That means you could just use `N = input()` (or `raw_input()`) and eliminate the `sys` stuff.
KennyTM
Also you could make smalls include teens, then the if statement would only be "if n < 20: return Smalls[n]". Smalls would still work for the >= 20 case, because of the modulus by 10.
Jon Smock
This must be the first time I see the (fully optional) `she-bang` in a code-golf answer ;-)
ChristopheD
0 doesn't work.
Ferruccio
Looks like a good start... Definitely tighten it up, even Python does not need ALL of this whitespace. :-) Also, as Ferruccio points out, 0 doesn't work, specifically it seems to go into an infinite loop.
Platinum Azure
@ChristopheD: Ruby parses the shebang by itself so there it *can* make a difference. No other language does that, as far as I'm aware, though.
Joey
+19  A: 

Python 2.x, 144 150 154 166 chars

This separates the number into tens and ones and sum them up. The undesirable property of the pseudo-ternary operator a and b or c that c is returned if b is 0 is being abused here.

n=input()
x=0x4d2d0f47815890bd2
while n-4:p=n<20and x/10**n%10or 44378/4**(n/10-2)%4+x/10**(n%10)%10+4;print n,"is %d."%p;n=p
print"4 is magic."

The previous naive version (150 chars). Just encode all lengths as an integer.

n=input()
while n-4:p=3+int('1yrof7i9b1lsi207bozyzg2m7sclycst0zsczde5oks6zt8pedmnup5omwfx56b29',36)/10**n%10;print n,"is %d."%p;n=p
print"4 is magic."
KennyTM
Sorry, I specifically wanted full stops just because of things like this. :-) Good entry though! (EDIT: I don't know Python, but could you `n,"is",p,"."`? I think you still save some chararacters if I'm counting right)
Platinum Azure
@Plat: That would cause an extra space before the `.`.
KennyTM
@KennyTM: Oh, duh, I should have noticed that even from the snippet. Oops! Well, anyway, as I said, some of the specifications were specifically designed to complicate things a bit. :-)
Platinum Azure
Can we shorten this by using a higher base than 36?
MikeD
@MikeD: Nope. From the Python docs: "The *base* parameter gives the base for the conversion (which is 10 by default) and may be any integer in the range [2, 36], or zero." Now you *might* be able to use a function other than `int()`, say something out of the `struct` or `base64` modules...
Mike DeSimone
+1 for being by KennyTM
willcodejavaforfood
Can somebody explain how this works?
Jonathan Sternberg
+9  A: 

Java (with boilerplate), 308 290 286 282 280 characters

class A{public static void main(String[]a){int i=4,j=0;for(;;)System.out.printf("%d is %s.%n",i=i==4?new java.util.Scanner(System.in).nextInt():j,i!=4?j="43354435543668877988699;::9;;:699;::9;;:588:998::9588:998::9588:998::97::<;;:<<;699;::9;;:699;::9;;:".charAt(i)-48:"magic");}}

I'm sure Groovy would get rid of much of that.

Explanation and formatting (all comments, newlines and leading/trailing whitespace removed in count):

Reasonably straight forward, but

//boilerplate
class A{
   public static void main(String[]a){
      //i is current/left number, j right/next number.  i=4 signals to start
      //by reading input
      int i=4,j=0;
      for(;;)
         //print in the form "<left> is <right>."
         System.out.printf(
            "%d is %s.%n",
            i=i==4?
               //<left>: if i is 4 <left> will be a new starting number
               new java.util.Scanner(System.in).nextInt():
               //otherwise it's the next val
               j,
            i!=4?
               //use string to map number to its length (:;< come after 9 in ASCII)
               //48 is value of '0'.  store in j for next iteration
               j="43354435543668877988699;::9;;:699;::9;;:588:998::9588:998::9588:998::97::<;;:<<;699;::9;;:699;::9;;:".charAt(i)-48:
               //i==4 is special case for right; print "magic"
               "magic");
   }
}

Edit: No longer use hex, this is less keystrokes

Mark Peters
249 without imports, class def or main def.
Mark Peters
That's fiendish. I like the base 16. (+1)
Platinum Azure
You could save one space by using `String[]a` instead of `String[] a`.
BalusC
Thanks @Balus, also eliminated a bunch by doing simple arithmetic on the character instead of using hex parsing.
Mark Peters
@Mark Peters: Even nastier. I feel so vanilla compared to that.
Platinum Azure
Alright it's as nasty as it gets now, and I've stopped trying to update the explanation with it. Now all of the logic happens as you evaluate the parameters to println :-) and I've removed the brackets that were making it the least bit readable, all to save 8 keystrokes.
Mark Peters
"I'm sure Groovy would get rid of much of that." ... Please demonstrate for those of us who have no idea what "Groovy" is or does.
Mike DeSimone
@Mike: Groovy is a multi-paradigm "superset" of Java that compiles to JVM bytecode. Almost every Java program is a valid Groovy program, but Groovy also does away with a lot of boilerplate. Notably, I believe you can do without the explicit class definition and `main` definition and just write code, like you can in perl, etc. I've never used it and don't have the dev environment though, so I'm not the one to post an example. Just imagine everything before `int...` being removed.
Mark Peters
+6  A: 

Perl: 148 characters

(Perl: 233 181 212 206 200 199 198 185 179 149 148 characters)

  • Moved exceptions hash into unit array. This resulted in my being able to cut a lot of characters :-)
  • mobrule pointed out a nasty bug. Quick fix adds 31 characters, ouch!
  • Refactored for zero special case, mild golfing done as well.
  • Direct list access for single use rather than storing to array? Hell yes!
  • SO MUCH REFACTORING for just ONE bloody character. This, truly, is the life of a golfer. :-(
  • Oops, easy whitespace fix. 198 now.
  • Refactored some redundant code.
  • Last return keyword in r is unnecessary, shaved some more off.
  • Massive refactoring per comments; unfortunately I could only get it to 149 because I had to fix a bug that was present in both my earlier code and the commenters' versions.
  • Trying bareword "magic".

Let's get this ball rolling with a modest attempt in Perl.

@u=split'','4335443554366887798866555766';$_=<>;chop;print"$_ is ".($_=$_==4?0:$_<20?$u[$_]:($u[$_/10+18]+($_%10&&$u[$_%10]))or magic).".
"while$_

Tricks:

Too many!

Platinum Azure
`r(20) = $t[0]+$u[0] = 10`?
mobrule
ACK! How I never tested that I'll never know.
Platinum Azure
Do you have some dead code in there? I'm not seeing how the special case for zero is necessary when $u[0] is 4. I have a seemingly-working version of your code @166 chars, and I think it has room to get a bit shorter than that.
hobbs
@hobbs: Good point, I'll look again. The story is that I got halfway through a couple of revisions and suddenly things broke (at about the point where I chose to have 4 --> 0). I think you're right at this point, though :-)
Platinum Azure
I don't consider myself a great Perl programmer, but you can reduce some characters: `@u=split$x,'43350435543668877988';` your commas use an unnecessary 19 characters, splitting on an `undef` splits at every character, i use `$x` as an undefined variable to take place of ` undef` -- total savings: 11 characters. Also, remove the `m` in `chomp` and you get another character shaved off your score.
vol7ron
Doing better, but you can still save more by losing `sub r` entirely -- you only use it once and you *can* replace it all by a single nested ternary without even parens. My version is 144 chars right now: http://gist.github.com/473289
hobbs
i decided not to cheat and just made it a one-liner substituting the newline for `\n`. With the `\n` it's 145, otherwise it would be 144/143 chars, depending if you count the linefeed as 1 or 0 characters.
vol7ron
By combining the lists and modifying the math: `@u=split'','4335043554366887798866555766';$_=<>;chop;print"$_ is ".($_=$_<20?$u[$_]:$u[$_/10+18]+($_%10?$u[$_%10]:0)or"magic").".\n"while$_` @ 139, or if whitespace is counted, you could turn that `\n` to a return and be at 137
vol7ron
M42
vol7ron
@vol7ron: How does your code deal with 0, 20, 24, 30, etc.? I'd have problems with getting those consistent in the past and I haven't got time to test.
Platinum Azure
Now I have it working... I'll see if I can find a way to get rid of the explicit case or at least shorten the conditional.
Platinum Azure
if you're using one of the later versions of Perl, you can use `say` instead of `print`. Not only is it shorter, it also appends a `\n` automatically to the end.
vol7ron
vol7ron
if still using `print` and `\n` it's **145**. If using `say` it's **141** characters
vol7ron
Platinum Azure
I believe you can shave off one character by getting rid of the quotes around `magic` (treating it as a bareword). The leading " will be replaced by a single space.
toolic
vol7ron
mobrule brought up a good point `$_==4?0:...` to `$_-4?...:0` to save another character
vol7ron
I was wrong about the bitwise-and, it was failing for values in the 90's and probably elsewhere.
vol7ron
@vol7ron: My worry is that I might need to think about precedence and insert parentheses; that was the reason for my original decision. I'd only add one more character at worst, though, and maybe get rid of one if I don't need it... If someone else wants to try it and then edit my answer if it does work for them, please feel free.
Platinum Azure
**@Platinum Azure:** I've already started my own. Check for my Perl answer currently at 121/125 (depending on version) :)
vol7ron
+4  A: 

C++ Stdio version, minified: 196 characters

#include <cstdio>
#define P;printf(
char*o="43354435543668877988";main(int p){scanf("%d",&p)P"%d",p);while(p!=4){p=p<20?o[p]-48:"0366555966"[p/10]-96+o[p%10]P" is %d.\n%d",p,p);}P" is magic.\n");}

C++ Iostreams version, minified: 195 characters

#include <iostream>
#define O;std::cout<<
char*o="43354435543668877988";main(int p){std::cin>>p;O p;while(p!=4){p=p<20?o[p]-48:"0366555966"[p/10]-96+o[p%10]O" is "<<p<<".\n"<<p;}O" is magic.\n";}

Original, un-minified: 344 characters

#include <cstdio>

int ones[] = { 4, 3, 3, 5, 4, 4, 3, 5, 5, 4, 3, 6, 6, 8, 8, 7, 7, 9, 8, 8 };
int tens[] = { 0, 3, 6, 6, 5, 5, 5, 9, 6, 6 };

int n(int n) {
    return n<20 ? ones[n] : tens[n/10] + ones[n%10];
}

int main(int p) {
    scanf("%d", &p);
    while(p!=4) {
        int q = n(p);
        printf("%i is %i\n", p, q);
        p = q;
    }
    printf("%i is magic\n", p);
}
Polybos
You need to read from stdin, sorry :-(
Platinum Azure
Fixed. It made it a bit shorter, too.
Mike DeSimone
Nicely done. (I laughed a lot at the 20-character std conundrum!)
Platinum Azure
Yeah, that was a real headbanger, until it dawned on me that `#define` would be even shorter since it could replace several tokens.
Mike DeSimone
`printf("is magic".\n)` => `puts`. `printf("%d",p)` => `puts(atoi(p))`. Not only shorter but faster too.
Ben Voigt
I shied away from those because I remember (from way back) that the interaction between `printf` and other output functions was not guaranteed. Specifically, some functions can go around the buffer that `printf` uses, resulting in out-of-order output. Has this been fixed?
Mike DeSimone
Oh, and I don't think `puts(atoi(p))` does what you think it does. Hint: there's no `itoa()`.
Mike DeSimone
@Mike DeSimone: I think `while(p!=4)` could be shortened to `while(p-4)`. One whole character, I know, but still. :-)
Platinum Azure
That's not valid C++. A standards compliant compiler should not compile it -- the names from the standard library need to have namespaces, or you need to `#include <stdio.h>` instead of `<cstdio>`.
Billy ONeal
@Billy a compiler that compiles code that includes <cstdio> and uses the global namespace versions _is_ conforming (or rather, compiling this code is not enough to disqualify the compiler), it's just not guaranteed. `cstdio` merely must provide definitions in the std namespace, it is also allowed to provide definitions in the global namespace. Likewise stdio.h must provide definitions in the global namespace, but it is also allowed to provide defintions in the std namespace. The problem is with the code, not the compiler.
Logan Capaldo
+1  A: 

Ruby, 164 characters

n=gets.to_i;s="03354435543668877987";if n==0;puts"0 is 4.";else;puts"#{n} is #{n=(n<20)?s[n]-48:"0066555766"[n/10]-48+s[n%10]-48}." until n==4;end;puts"4 is magic."

decoded:

n = gets.to_i
s = "03354435543668877987"
if n == 0
  puts "0 is 4."
else
  puts "#{n} is #{n = (n < 20) ? s[n] - 48 : "0066555766"[n / 10] - 48 + s[n % 10] - 48}." until n == 4
end

puts "4 is magic."
Jon Smock
Nice Ruby solution, keeping it simple. :-) (+1)
Platinum Azure
Keeping it simple is no excuse for keeping it overly long, though ;-)
Joey
I think you can replace 'if n==0' with 'if !n'
Vincent
In Ruby? I always thought that all values except false and nil evaluated to true :-(
Platinum Azure
@Platinum Azure: Sorry, you're right x_X
Vincent
+6  A: 

C, 158 characters

main(n,c){char*d="03354435543668877988";for(scanf("%d",&n);n-4;n=c)printf("%d is %d.\n",n,c=n?n<19?d[n]-48:d[n%10]-"_,**+++)**"[n/10]:4);puts("4 is magic.");}

(originally based on Vlad's Python code, borrowed a trick from Tom Sirgedas' C++ solution to squeeze out a few more characters)

expanded version:

main(n, c) {
    char *d = "03354435543668877988";
    for (scanf("%d",&n); n-4; n = c)
        printf("%d is %d.\n", n, c = n ? n<19 ? d[n]-48 : d[n%10] - "_,**+++)**"[n/10]  : 4);
    puts("4 is magic.");
}
Ferruccio
Doesn't seem to work for me: ./magic 1010 is -27.Segmentation fault
Casey
Ferruccio
works for me now :D nice.
Casey
You can save 3 characters by replacing "_466555766"[n/10]+d[n%10]-96 with d[n%10]-"_,**+++)**"[n/10]
Tom Sirgedas
nice trick. thanks Tom.
Ferruccio
A: 

PhP Code

function get_num_name($num){  
    switch($num){  
        case 1:return 'one';  
    case 2:return 'two';  
    case 3:return 'three';  
    case 4:return 'four';  
    case 5:return 'five';  
    case 6:return 'six';  
    case 7:return 'seven';  
    case 8:return 'eight';  
    case 9:return 'nine';  
    }  
}  

function num_to_words($number, $real_name, $decimal_digit, $decimal_name){  
    $res = '';  
    $real = 0;  
    $decimal = 0;  

    if($number == 0)  
        return 'Zero'.(($real_name == '')?'':' '.$real_name);  
    if($number >= 0){  
        $real = floor($number);  
        $decimal = number_format($number - $real, $decimal_digit, '.', ',');  
    }else{  
        $real = ceil($number) * (-1);  
        $number = abs($number);  
        $decimal = number_format($number - $real, $decimal_digit, '.', ',');  
    }  
    $decimal = substr($decimal, strpos($decimal, '.') +1);  

    $unit_name[1] = 'thousand';  
    $unit_name[2] = 'million';  
    $unit_name[3] = 'billion';  
    $unit_name[4] = 'trillion';  

    $packet = array();    

    $number = strrev($real);  
    $packet = str_split($number,3);  

    for($i=0;$i<count($packet);$i++){  
        $tmp = strrev($packet[$i]);  
        $unit = $unit_name[$i];  
        if((int)$tmp == 0)  
            continue;  
        $tmp_res = '';  
        if(strlen($tmp) >= 2){  
            $tmp_proc = substr($tmp,-2);  
            switch($tmp_proc){  
                case '10':  
                    $tmp_res = 'ten';  
                    break;  
                case '11':  
                    $tmp_res = 'eleven';  
                    break;  
                case '12':  
                    $tmp_res = 'twelve';  
                    break;  
                case '13':  
                    $tmp_res = 'thirteen';  
                    break;  
                case '15':  
                    $tmp_res = 'fifteen';  
                    break;  
                case '20':  
                    $tmp_res = 'twenty';  
                    break;  
                case '30':  
                    $tmp_res = 'thirty';  
                    break;  
                case '40':  
                    $tmp_res = 'forty';  
                    break;  
                case '50':  
                    $tmp_res = 'fifty';  
                    break;  
                case '70':  
                    $tmp_res = 'seventy';  
                    break;  
                case '80':  
                    $tmp_res = 'eighty';  
                    break;  
                default:  
                    $tmp_begin = substr($tmp_proc,0,1);  
                    $tmp_end = substr($tmp_proc,1,1);  

                    if($tmp_begin == '1')  
                        $tmp_res = get_num_name($tmp_end).'teen';  
                    elseif($tmp_begin == '0')  
                        $tmp_res = get_num_name($tmp_end);  
                    elseif($tmp_end == '0')  
                        $tmp_res = get_num_name($tmp_begin).'ty';  
                    else{  
                        if($tmp_begin == '2')  
                            $tmp_res = 'twenty';  
                        elseif($tmp_begin == '3')  
                            $tmp_res = 'thirty';  
                        elseif($tmp_begin == '4')  
                            $tmp_res = 'forty';  
                        elseif($tmp_begin == '5')  
                            $tmp_res = 'fifty';  
                        elseif($tmp_begin == '6')  
                            $tmp_res = 'sixty';  
                        elseif($tmp_begin == '7')  
                            $tmp_res = 'seventy';  
                        elseif($tmp_begin == '8')  
                            $tmp_res = 'eighty';  
                        elseif($tmp_begin == '9')  
                            $tmp_res = 'ninety';  

                        $tmp_res = $tmp_res.' '.get_num_name($tmp_end);  
                    }  
                    break;  
            }  

            if(strlen($tmp) == 3){  
                $tmp_begin = substr($tmp,0,1);  

                $space = '';  
                if(substr($tmp_res,0,1) != ' ' && $tmp_res != '')  
                    $space = ' ';  

                if($tmp_begin != 0){  
                    if($tmp_begin != '0'){  
                        if($tmp_res != '')  
                            $tmp_res = 'and'.$space.$tmp_res;  
                    }  
                    $tmp_res = get_num_name($tmp_begin).' hundred'.$space.$tmp_res;  
                }  
            }  
        }else  
            $tmp_res = get_num_name($tmp);  
        $space = '';  
        if(substr($res,0,1) != ' ' && $res != '')  
            $space = ' ';  
        $res = $tmp_res.' '.$unit.$space.$res;  
    }  

    $space = '';  
    if(substr($res,-1) != ' ' && $res != '')  
        $space = ' ';  

    if($res)  
        $res .= $space.$real_name.(($real > 1 && $real_name != '')?'s':'');  

    if($decimal > 0)  
        $res .= ' '.num_to_words($decimal, '', 0, '').' '.$decimal_name.(($decimal > 1 && $decimal_name != '')?'s':'');  
    return ucfirst($res);  
}  

//////////// testing ////////////////

 $str2num = 12;
    while($str2num!=4){
        $str = num_to_words($str2num, '', 0, '');  
        $str2num = strlen($str)-1;
        echo $str . '=' . $str2num .'<br/>';
        if ($str2num == 4)
            echo 'four is magic';
    }

////// Results /////////

Twelve =6
Six =3
Three =5
Five =4
four is magic
Ghost
a tad bit overcomplicated.
Wallacoloo
@wallacoloo: A crummy solution for a crummy language :D
trinithis
Or a much shorter 178 characters: `$l='4335443554366887798866555766';for($b=(int)fgets(fopen('php://stdin','r'));($a=$b)-4;){$b=$a<20?$l[$a]:$l[18+$a/10]+($a%10?$l[$a%10]:0);echo"$a is $b.\n";}echo"4 is magic.\n";`
gnarf
Nice joke. :-D So well programmed.
tomp
+1  A: 

C++, 171 characters (#include omitted)

void main(){char x,y,*a="03354435543668877988";scanf("%d",&x);for(;x-4;x=y)y=x?x<19?a[x]-48:"_466555766"[x/10]+a[x%10]-96:4,printf("%d is %d.\n",x,y);puts("4 is magic.");}
Tom Sirgedas
Gotta count the `#include`.
Mike DeSimone
I think if you consider this to be C, you can avoid the need for the `#include` because the functions will just be assumed to take `int` parameters. You can even save a stroke by making `main` return `int`.
Gabe
+3  A: 

C# 314 286 283 274 289 273 252 chars.

Squished:

252 

Normal:

using C = System.Console;
class P
{
    static void Main()
    {
        var x = "4335443554366877798866555766";
        int m, o, v = int.Parse(C.ReadLine());
        do {
            C.Write("{0} is {1}.\n", o = v, v == 4 ? (object)"magic" : v = v < 20 ? x[v] - 48 : x[17 + v / 10] - 96 + ((m = v % 10) > 0 ? x[m] : 48));
        } while (o != 4);
        C.ReadLine();
    }
}

Edit Dykam: Did quite some carefull insertions and changes:

  • Changed the l.ToString() into a cast to object of the string "magic".
  • Created a temporary variable o, so I could move the break outside the for loop, that is, resulting in a do-while.
  • Inlined the o assignment, aswell the v assignment, continueing in inserting the calculation of l in the function arguments altogether, removing the need for l. Also inlined the assignment of m.
  • Removed a space in int[] x, int[]x is legit too.
  • Tried to transform the array into a string transformation, but the using System.Linq was too much to make this an improvement.

Edit 2 Dykam Changed the int array to a char array/string, added proper arithmics to correct this.

mdm20
Yeah, got it shorter than the Java version.
Dykam
+5  A: 

C#: 210 Characters.

Squished:

using C=System.Console;class B{static void Main(){int
x=0,y=int.Parse(C.ReadLine());while(x!=4)C.Write((x=y)+" is {0}.\n",x==4?"magic":""+(y=x==0?4:"03354435543668877988"[x<20?x:x%10]+"0066555766"[x/10]-96));}}

Expanded:

using C=System.Console;
class B
{
    static void Main()
    {
        int x=0,y=int.Parse(C.ReadLine());
        while(x!=4)
            C.Write((x=y)+" is {0}.\n",
                x==4?
                     "magic":
                     ""+(y= x==0?
                                4:
                                "03354435543668877988"[x<20?x:x%10]+
                                "0066555766"[x/10]-96)
                   );
    }
}

Tricks this approach uses:

  • Create a lookup table for number name lengths based on digits that appear in the number.
  • Use character array lookup on a string, and char arithmetic instead of a numeric array.
  • Use class name aliasing to short Console. to C.
  • Use the conditional (ternary) operator (?:) instead of if/else.
  • Use the \n with Write escape code instead of WriteLine
  • Use the fact that C# has a defined order of evaluation to allow assignments inside the Write function call
  • Use the assignment expressions to eliminate extra statements, and thus extra braces
LBushkin
`int[] z` would be shorter since it doesn't need the `new[]`
Joey
Revised to use character arithmetic instead of array lookup.
LBushkin
This code doesn't work for a lot of numbers... 19, 44, etc.
mdm20
@mdm20: You're right. I had a mistake in the lookup table. Fixed now.
LBushkin
check it again, still not right :P
mdm20
@mdm20: Which cases fail?
LBushkin
45, 50, 70 that I tried.
mdm20
Well, twelfth time is the charm :*D
LBushkin
A quicky to save 5 characters: Shorter than casting `"magic"` to `object`, would be to *implicitly* call `ToString()` on `y` by adding `""`. But, because `+` has higher precedence than `?:`, you have to put it in the *true* part instead of the *false* part: `x!=4?y+"":"magic"`.
P Daddy
I got it down to a respectable 212 by using PDaddy's suggestion, breaking up the lookup table into two parts, and converting the `do while` loop into `while`.
Gabe
OK, now I got it down to 210!
Gabe
Only one it seems to fail on now is #20.
thekaido
+25  A: 

Common Lisp 157 Chars

New more conforming version, now reading form standard input and ignoring spaces and hyphens:

(labels((g (x)(if(= x 4)(princ"4 is magic.")(let((n(length(remove-if(lambda(x)(find x" -"))(format nil"~r"x)))))(format t"~a is ~a.~%"x n)(g n)))))(g(read)))

In human-readable form:

 (labels ((g (x)
           (if (= x 4)
            (princ "4 is magic.")
            (let ((n (length (remove-if (lambda(x) (find x " -"))
                                        (format nil "~r" x)))))
               (format t"~a is ~a.~%" x n)
               (g n)))))
    (g (read)))

And some test runs:

>24
24 is 10.
10 is 3.
3 is 5.
5 is 4.
4 is magic.

>23152436
23152436 is 64.
64 is 9.
9 is 4.
4 is magic.

And the bonus version, at 165 chars:

 (labels((g(x)(if(= x 4)(princ"four is magic.")(let*((f(format nil"~r"x))(n(length(remove-if(lambda(x)(find x" -"))f))))(format t"~a is ~r.~%"f n)(g n)))))(g(read)))

Giving

>24
twenty-four is ten.
ten is three.
three is five.
five is four.
four is magic.

>234235
two hundred thirty-four thousand two hundred thirty-five is forty-eight.
forty-eight is ten.
ten is three.
three is five.
five is four.
four is magic.
johanbev
Okay, now show us how the Bonus is done. Shouldn't be too much different, right? BTW, does Common Lisp say [a billion is 10^9 (short scale) or 10^12 (long scale)?](http://en.wikipedia.org/wiki/Long_and_short_scales)
Mike DeSimone
I thought "twenty-four" only has 10 letters?
KennyTM
The numbers after "is" should be text, too.
Mike DeSimone
@KennyTM, depends on if "long-term" is a 9 or 8 letter word, I'm not a native speaker, but in Norway we count hyphens and spaces as letters when talking about word-length.
johanbev
@Mike DeSimone Feel free to edit, I like this version because it gives counts as well, making it easier to verify.
johanbev
I think this violates rule #2 - input must be read from standard input.
Ferruccio
@johanbev: The spec says "ignore a hyphen or spaces or "and"".
KennyTM
why is this so high up? other ones don't use a built-in format function and they are less characters
Claudiu
@Claudiu Because Common Lisp is awesome.
Dur4ndal
I know the spec is a little arbitrary, but I did specifically state not to count hyphens or spaces or "and", strictly letters that form the component numerical words. :-)
Platinum Azure
@johanbev: As Mike DeSimone says, the numbers after "is" should be text too. Sorry for not making that clear (I see now that I didn't actually put that explicitly in the spec). I'll fix that shortly.
Platinum Azure
As KennyTM said, this is an invalid answer. Example from question: `42 is 8` ignoring spaces and hyphens it's 8, your way has it listed as 9, which means you are not ignoring.
vol7ron
It doesn't matter how many strokes you take if you don't get the ball in the hole. People seem to forget that when they upvote incorrect solutions.
Mark Peters
The bonus one should properly capitalize the first word in its sentences!!! English FTW!
trinithis
+9  A: 

Windows PowerShell: 152 153 184 bytes

based on the previous solution, with more influence from other solutions

$o="03354435543668877988"
for($input|sv b;($a=$b)-4){if(!($b=$o[$a])){$b=$o[$a%10]-48+"66555766"[($a-$a%10)/10-2]}$b-=48-4*!$a
"$a is $b."}'4 is magic.'
Joey
Fixed to support multiples of 10 ("ninety" rather than "ninetyzero").
Gabe
Hey @Gabe :), thanks; haven't had much time for golfing lately. Still, the quotes around `$input` have to remain since you can't cast an enumerator directly to `int`; it works when going through `string` first :-)
Joey
+1  A: 

Lua 185 190 199

added periods, added io.read, removed ()'s on last print

 n=io.read();while(n~=4)do m=('43354435543668877988699;::9;;:699;::9;;:588:998::9588:998::9588:998::97::<;;:<<;699;::9;;:699;::9;;:'):sub(n+1):byte()-48;print(n,' is ',m,'.')n=m;end print'4 is magic.'

with line breaks

 n=io.read()
 while (n~=4) do
    m=('43354435543668877988699;::9;;:699;::9;;:588:998::9588:998::9588:998::97::<;;:<<;699;::9;;:699;::9;;:'):sub(n+1):byte()-48;
    print(n,' is ',m,'.')
    n=m;
 end 
 print'4 is magic.'
Nick
Needs a `n=io.read()` (+11 chars) to comply with rule to read number from standard input. Changing `print('4 is magic.')` to `print'4 is magic.'` will save 2 chars. Removing `;` after `)` will save 1 char. The `print` using commas seems like cheating, but spec is unclear. Might as well change it to `print(n,'is',m,'.')` to save 2 chars.
gwell
Are commas rendered as newlines in Lua stand alone? It's been a while since Ive used it.
Nick
@Nick: The commas are rendered as tabs.
gwell
+4  A: 

Haskell, 224 270 characters

o="43354435543668877988"
x!i=read[x!!i]
n x|x<20=o!x|0<1="0066555766"!div x 10+o!mod x 10
f x=zipWith(\a b->a++" is "++b++".")l(tail l)where l=map show(takeWhile(/=4)$iterate n x)++["4","magic"]
main=readLn>>=mapM putStrLn.f

And little more readable -

ones = [4,3,3,5,4,4,3,5,5,4,3,6,6,8,8,7,7,9,8,8]
tens = [0,0,6,6,5,5,5,7,6,6]

n x = if x < 20 then ones !! x else (tens !! div x 10) + (ones !! mod x 10)

f x = zipWith (\a b -> a ++ " is " ++ b ++ ".") l (tail l)
    where l = map show (takeWhile (/=4) (iterate n x)) ++ ["4", "magic"]

main = readLn >>= mapM putStrLn . f
Matajon
+16  A: 
P Daddy
+1 for the explanations
Matias
20 doesn't seem right "six is six" ?
Enriquev
@Enriquev: Gah! Transcription error. Thanks, fixed.
P Daddy
@Enriquev: Did you seriously down-vote me for that?
P Daddy
This is easily my favorite of the word answers... Bravo, well done. +1 for you, and if I could give two checkmarks I would.
Platinum Azure
Thanks!*** *** *** ***
P Daddy
That's fun to read, I think I will use these numbers from now on in daily life. Six, sem, eight, nine, tel, elem, twelve, enpee, fourpee, fifpee, sixpee, sevenpee, eightoh, ninepee, twelkyu... =)
deceze
@deceze: LOL :)
P Daddy
+49  A: 

GolfScript - 101 96 93 92 91 90 94 86 bytes

90 → 94: Fixed output for multiples of 10.
94 → 86: Restructured code. Using base 100 to remove non-printable characters.
86 → 85: Shorter cast to string.

{n+~."+#,#6$DWOXB79Bd")base`1/10/~{~2${~1$+}%(;+~}%++=" is "\".
"1$4$4-}do;;;"magic."
Nabb
why is this so far down? it's shorter than the lisp one and doesn't use a built-in formatting function
Claudiu
@Claudiu: Inertia.
KennyTM
+1 to help bring it above Lisp. :-)
Platinum Azure
I like how the code ends with `"magic."`, it pretty much sums it up.
Aistina
@Aistina: That's easy to do in this challenge, I think. :-)
Platinum Azure
@Aistina: haha, that is kinda funny. "mumbo jumbo yada yada..magic"
vol7ron
GolfScript should be considered a hack. I'm glad it beats Lisp, but in the real rules of Golf, there is no GolfScript allowed :)
vol7ron
That string looks awfully familiar. ;) What's the "d" at the end for?
P Daddy
@P Daddy The `d` is extracted by the `)` as `100` and is used as the radix for the base conversion.
Nabb
I think this is the clear winner at this point. I'm accepting this now in case the question ends up being closed... Well done, Nabb! (and KennyTM too)
Platinum Azure
I love how this doesn't actually contain the words "one, two, three, four" etc like the other answers do.
Dolph
@vol7ron: how come?
Claudiu
@Claudiu: because it's a language that was specifically designed for the game of golf, hence the name **Golf** Script. It's like going fishing with dynamite. You'll get the job done, but you miss the art in doing it. Generally, traditional languages are the only ones allowed.
vol7ron
@vol7ron: golfscript is turing-complete, though. it can do anythig a traditional language can do (and one of the projects i want to do soon is to write a core javascript to golfscript compiler). it's pretty much J without so many built-ins, but people seem to allow J in these competitions.
Claudiu
+3  A: 

Delphi: 329 characters

Single Line Version:

program P;{$APPTYPE CONSOLE}uses SysUtils;const S=65;A='EDDFEEDFFEDGGIIHHJII';B='DGGFFFJGG';function Z(X:Byte):Byte;begin if X<20 then Z:=Ord(A[X+1])-S else Z:=(Ord(B[X DIV 10])-S)+Z(X MOD 10)end;var X,Y:Byte;begin Write('> ');ReadLn(X);repeat Y:=Z(X);WriteLn(Format('%d is %d.',[X,Y]));X:=Y;until X=4;WriteLn('4 is magic.');end.

Formated:

program P;

{$APPTYPE CONSOLE}

uses
  SysUtils;

const
  S = 65;
  A = 'EDDFEEDFFEDGGIIHHJII';
  B = 'DGGFFFJGG';

function Z(X:Byte):Byte;
begin
  if X<20
  then Z := Ord(A[X+1])-S
  else Z := (Ord(B[X DIV 10])-S) + Z(X MOD 10);
end;

var
  X,Y: Byte;

begin
  Write('> ');
  ReadLn(X);

  repeat
    Y:=Z(X);
    WriteLn(Format('%d is %d.' , [X,Y]));
    X:=Y;
  until X=4;

  WriteLn('4 is magic.');
end.

Probably room for some more squeezing... :-P

Jørn E. Angeltveit
+5  A: 

JavaScript 1.8 (SpiderMonkey) - 153 Chars

l='4335443554366887798866555766'.split('')
for(b=readline();(a=+b)-4;print(a,'is '+b+'.'))b=a<20?l[a]:+l[18+a/10|0]+(a%10&&+l[a%10])
print('4 is magic.')

Usage: echo 42 | js golf.js

Output:

42 is 8.
8 is 5.
5 is 4.
4 is magic.

With bonus - 364 chars

l='zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty thirty fourty fifty sixty seventy eighty ninety'.split(' ')
z=function(a)a<20?l[a]:l[18+a/10|0]+(a%10?' '+l[a%10]:'')
for(b=+readline();(a=b)-4;print(z(a),'is '+z(b)+'.'))b=z(a).replace(' ','').length
print('four is magic.')

Output:

ninety nine is ten.
ten is three.
three is five.
five is four.
four is magic.
gnarf
+9  A: 

T-SQL, 413 451 499 chars

CREATE FUNCTION d(@N int) RETURNS int AS BEGIN
Declare @l char(50), @s char(50)
Select @l='0066555766',@s='03354435543668877987'
if @N<20 return 0+substring(@s,@N+1,1) return 0+substring(@l,(@N/10)+1,1) + 0+(substring(@s,@N%10+1,1))END
GO
CREATE proc M(@x int) as BEGIN
WITH r(p,n)AS(SELECT p=@x,n=dbo.d(@x) UNION ALL SELECT p=n,n=dbo.d(n) FROM r where n<>4)Select p,'is',n,'.' from r print '4 is magic.'END

(Not that I'm seriously suggesting you'd do this... really I just wanted to write a CTE)

To use:

M 95

Returns

p                n
----------- ---- -----------
95          is   10.
10          is   3.
3           is   5.
5           is   4.
4 is magic.
Leon Bambrick
Can't you just print the individual results instead of returning a table? That would make the output look nicer.
Joey
I don't think it handles zero properly. How about something like this: `CREATE FUNCTION d(@ int) RETURNS int AS BEGIN Declare @l char(9),@s char(50) Select @l='066555766',@s='03354435543668877987' if @=0 return 4 if @<20 return 0+substring(@s,@+1,1)return 0+substring(@l,@/10,1)+substring(@s,@%10+1,1)END`
Gabe
+3  A: 

C - without number words

180 175* 172 167 characters

All newlines are for readability and can be removed:

i;V(x){return"\3#,#6$:WOXB79B"[x/2]/(x%2?1:10)%10;}main(c){for(scanf("%d",&c);
c-4;)i=c,printf("%d is %d.\n",i,c=c?c>19?V(c/10+19)+V(c%10):V(c):4);puts(
"4 is magic.");}

Slightly unminified:

i;
V(x){return"\3#,#6$:WOXB79B"[x/2]/(x%2?1:10)%10;}
main(c){
    for(scanf("%d",&c);c-4;)
        i=c,
        printf("%d is %d.\n",i,c=c?c>19?V(c/10+19)+V(c%10):V(c):4);
    puts("4 is magic.");
}

* The previous version missed the mark on two parts of the spec: it didn't handle zero, and it took input on the command line instead of stdin. Handling zero added characters, but using stdin instead of command line args saved even more, resulting in a net savings.

P Daddy
+68  A: 

Perl, about 147 char

Loosely based on Platinum Azure's solution:

               chop
              ($_.=
              <>);@
             u="433
            5443554
           366  887
          798   866
         555    766
        "=~     /\d
       /gx      ;#4
      sub       r{4
     -$_        ?$_
    <20         ?$u
   [$_          ]:(
  $'?           $u[
 $']            :0)
+$u[18+$&]:magic}print"
$_ is ",$_=r(),'.'while
                /\d
                /x;
                444
mobrule
That's pretty impressive.
Josh K
Hobbs and I'd like to get a little credit :)
vol7ron
@Platinum Azure the way this gets it's input is through the use of `pop`, without any arguments. Outside of a subroutine `pop` removes, and returns the last value of `@ARGV` which is the list of arguments to the Perl program. It could just as easily be replaced with `shift`, but that adds another 2 characters. See: http://p3rl.org/pop
Brad Gilbert
it looks like you need a newline character in `'.'`, which is 2 for `\n` or 1 if you're counting whitespace in the `'. '` (space being the newline literal)
vol7ron
A bit longer, but creativity goes a long way in my book.
Beska
@Platinum Azure et al: He is getting his input from stdin. That's the way to do it in Perl. (Maybe he changed it after your comment?)
@dehmann: Yeah, he did. I'll delete my other comment.
Platinum Azure
it has style...
Adam Shiemke
I love how you've **4matted** your code.
P Daddy
@P Daddy: *groan* but +1 to your comment anyway
Platinum Azure
Nicely done Pun @Daddy. You too mobrule.
Mark Ransom
Great formatting!
Séb
+1  A: 
vol7ron
mobrule
+1 Nicely done. You've gone out of stdin into args though :-(
Platinum Azure
Right, replaced with `ARGV`, which is populated by `STDIN` :) or.. `echo bar | xargs perl foo.pl`, technically piped from echo into args for perl :)
vol7ron
+6  A: 

Python, 129 133 137 148 chars

As a warm-up, here is my first version (improves couple of chars over previous best Python).

PS. After a few redactions now it is about twenty char's shorter:

n=input()
while n-4:p=(922148248>>n/10*3&7)+(632179416>>n%10*3&7)+(737280>>n&1)+4*(n<1);print n,'is %d.'%p;n=p
print'4 is magic.'
Nas Banov
Brilliant! ** **
P Daddy
A: 
while(true)
{
    string a;
    ReadLine(a)
    WriteLine(4);

}
What?` ` ` ` ` `
Josh K
This is indeed *magic*.
0xA3
I want to know who upvoted this...
Josh K
A: 

Shameless Perl with Number Words (329 characters)

Adapted fairly directly from P Daddy's C code, with some tweaks to p() to make it do the same thing using Perl primitives instead of C ones, and a mostly-rewritten mainloop. See his for an explanation. Newlines are all optional.

@t=(qw(zero one two three four five six sM eight nine
tL elM twelve NP 4P fifP 6P 7P 8O 9P twLQ NQ forQ fifQ
6Q 7Q 8y 9Q en evL thir eL tO ty 4SmagicT)," is ",".\n");
sub p{local$_=$t[pop];1while s/[0-Z]/$t[-48+ord$&]/e;
print;length}$_=<>;chop;while($_-4){
$_=($_>19?(p($_/10+18),$_&&print("-"),$_%=10)[0]:0)+p$_;
p 35;p$_;p 36}p 34

Side note: it's too bad that perl print just returns true/false; if it returned a count it would save me 7 strokes.

hobbs
+3  A: 

Lua, 176 Characters

o={[0]=4,3,3,5,4,4,3,5,5,4,3,6,6,8,8,7,7,9,8,8}t={3,6,6,5,5,5,7,6,6}n=0+io.read()while n~=4 do a=o[n]or o[n%10]+t[(n-n%10)/10]print(n.." is "..a..".")n=a end print"4 is magic."

or

  o={[0]=4,3,3,5,4,4
  ,3,5,5,4,3,6,6,8,8
  ,7,7,9,8,8}t={3,6,
   6,5,5,5,7,6,6}n=
   0+io.read()while
   n ~= 4 do a= o[n
   ]or o[n%10]+t[(n
   -n%10)/10]print(
n.." is "..a.."." )n=a
end print"4 is magic."
gwell
That a magician's top hat or something?
Platinum Azure
+2  A: 

perl, 123 122 characters

Just realized that there is no requirement to output to STDOUT, so output to STDERR instead and knock off another character.

@u='0335443554366887798866555766'=~/./g;$_+=<>;warn"$_ is ",$_=$_-4?$_<20?$u[$_]||4:$u[chop]+$u[$_+18]:magic,".\n"until/g/

And, a version that returns spelled out numbers:

279 278 276 280 characters

@p=(Thir,Four,Fif,Six,Seven,Eigh,Nine);@n=("",One,Two,Three,Four,Five,@p[3..6],Ten,Eleven,Twelve,map$_.teen,@p);s/u//for@m=map$_.ty,Twen,@p;$n[8].=t;sub n{$n=shift;$n?$n<20?$n[$n]:"$m[$n/10-2] $n[$n%10]":Zero}$p+=<>;warnt$m=n($p)," is ",$_=$p-4?n$p=()=$m=~/\w/g:magic,".\n"until/c/

While that meets the spec, it is not 100% well formatted. It returns an extra space after numbers ending in zero. The spec does say:

"I don't care how you separate the word tokens, though they should be separated"

That's kind of weaselly though. A more correct version at

282 281 279 283 characters

@p=(Thir,Four,Fif,Six,Seven,Eigh,Nine);@n=("\x8",One,Two,Three,Four,Five,@p[3..6],Ten,Eleven,Twelve,map$_.teen,@p);s/u//for@m=map$_.ty,Twen,@p;$n[8].=t;sub n{$n=shift;$n?$n<20?$n[$n]:"$m[$n/10-2]-$n[$n%10]":Zero}$p+=<>;warn$m=n($p)," is ",$_=$p-4?n$p=()=$m=~/\w/g:magic,".\n"until/c/
thundergnat