24
\$\begingroup\$

I'm a mentor at RubyLearning and one of the exercises we give to our students is the "Deaf Grandma" exercise from Chris Pine's book "Learn to Program". Here's the description:

Write a Deaf Grandma program. Whatever you say to grandma (whatever you type in), she should respond with: "Huh?! Speak up, sonny!", unless you shout it (type in all capitals). If you shout, she can hear you (or at least she thinks so) and yells back: "No, not since 1938!"

To make your program really believable, have grandma shout a different year each time; maybe any year at random between 1930 and 1950. (This part is optional, and would be much easier if you read the section on Ruby's random number generator at the end of the methods chapter.) You can't stop talking to grandma until you shout "BYE".

After several course iterations I tried to see how small I can get this and now have it down to 112 characters:

puts (s||='').upcase==s ? "NO, NOT SINCE #{1930+rand(21)}!":"HUH?! SPEAK UP, SONNY!" until(s=gets.chomp)=="BYE"

I'm curious to see in how few characters this can be achieved in the language of your choice, because I think Ruby is already doing really well here.

Edit: The Perl solution posted below led to

ruby -nle 'puts($_=="BYE"?exit: $_.upcase!? "HUH?! SEPAK UP, SONNY!":"NO, NOT SINCE #{1930+rand(21)}!")'

which is 92 characters for the expression + 2 more for the n and l options.

\$\endgroup\$
2
  • \$\begingroup\$ In a golfing context, this needs additional specification. What should happen if there's any extra output past BYE? \$\endgroup\$
    – J B
    Commented Feb 4, 2011 at 15:27
  • \$\begingroup\$ Only "BYE" exactly terminates the program. \$\endgroup\$ Commented Feb 4, 2011 at 20:54

20 Answers 20

13
\$\begingroup\$

Perl, 85 91

Run with perl -nE '<code goes there>' (n counted in program size):

$==1930+rand 21;say/^BYE$/?last:uc eq$_?"
NO, NOT SINCE $=!":"HUH?! SPEAK UP, SONNY!"

That trailing exclamation mark is very expensive...

Edits suggested by IK:

  • Using a regexp instead of a string match spares the -l global option as well as two program characters: -3.
  • Using an actual variable to save a value and use it later for interpolation (Genius! Who'd have thought of using a variable for that?): 0.
  • Making that variable $=, constrained to be an integer: -4.

(and it still doesn't add up and I'm too sleepy to find out why. Oh well, the final count is right at least)

\$\endgroup\$
2
  • \$\begingroup\$ Abusing $= and using a regexp for "BYE" gets this down to 84+1: perl -nE '$==1930+rand 21;say/^BYE$/?last:uc eq$_?"NO, NOT SINCE $=!":"HUH?! SPEAK UP, SONNY!"' \$\endgroup\$ Commented Sep 28, 2012 at 8:26
  • \$\begingroup\$ @IlmariKaronen Integrated, thanks! \$\endgroup\$
    – J B
    Commented Sep 29, 2012 at 9:50
6
\$\begingroup\$

Python 120 Characters

r=raw_input
s=r()
while'BYE'!=s:
 print["HUH?! SPEAK UP, SONNY!","NO, NOT SINCE %d!"%(1930+id(s)%21)][s.isupper()];s=r()

Any hints to improve?

\$\endgroup\$
3
  • \$\begingroup\$ You don't need the brackets around that if statement, also I'm pretty sure python has a recursion ceiling - but that could just simulate your grandma falling asleep. \$\endgroup\$
    – Phoshi
    Commented Feb 4, 2011 at 14:17
  • \$\begingroup\$ Oh! I forgot to remove the brackets. Thanks :) \$\endgroup\$
    – fR0DDY
    Commented Feb 4, 2011 at 16:25
  • \$\begingroup\$ You can save some characters if you remove the first line, replace the second with s='', reorder your statements in your while loop, and put the whole while loop on one line: gist.github.com/3787809 If you were really determined, you could save 2 characters by using python 3 (raw_input() -> input(), but print -> print()) \$\endgroup\$
    – Matt
    Commented Sep 26, 2012 at 12:49
4
\$\begingroup\$

C# - 194 CHARS

using System;class P{static void Main(){var s=Console.ReadLine();if(s!="BYE"){Console.Write((s==s.ToUpper()?"No, not since 19"+new Random().Next(30, 51):"Huh?! Speak up, sonny")+"!");Main();}}}

With whitespaces:

using System;
class P
{
    static void Main()
    {
        var s = Console.ReadLine();
        if (s != "BYE")
        {
            Console.Write((s == s.ToUpper() ? "No, not since 19" + new Random().Next(30, 51) : "Huh?! Speak up, sonny") + "!");
            Main();
        }
    }
}

With some inspiration from Nellius and fR0DDY.

Please let me know if it can be improved.

\$\endgroup\$
5
  • \$\begingroup\$ Short, but FWIW, i think this leaks (recursively calling Main()). Also, i think you want parens in the ?: expression to get the ! in both. I added an answer with this and EOL (but still leaks). \$\endgroup\$
    – b w
    Commented Feb 4, 2011 at 16:54
  • \$\begingroup\$ i see you added the parens and removed your comment. Good job. Now my edit to add screenshots to my answer of with and without the parens is moot. (But, still leaky) :-) \$\endgroup\$
    – b w
    Commented Feb 4, 2011 at 17:58
  • \$\begingroup\$ @bill Yes, I messed my testing initially. The non leaky version would be 199 chars, waaay too long :) \$\endgroup\$
    – Richard
    Commented Feb 4, 2011 at 18:07
  • \$\begingroup\$ ha. I like the Main(); solution... no sane person would use this program long enough for it to be a problem. \$\endgroup\$
    – b w
    Commented Feb 4, 2011 at 18:10
  • \$\begingroup\$ As Phoshi said in the comment to fR0DDY. The program crashes when grandma falls asleep. \$\endgroup\$
    – Richard
    Commented Feb 4, 2011 at 18:19
4
\$\begingroup\$

131 characters in PowerShell:

for(){$j=read-host;if($j-ceq"BYE"){break}if($j-ceq$j.ToUpper()){"No, not since 19$(10..90|random)!"}else{"Huh?! Speak up, sonny!"}}

W/ whitespace:

for(){
  $j = read-host;
  if ( $j -ceq "BYE" ) { break }
  if ( $j -ceq $j.ToUpper() ) { "No, not since 19$(10..90|random)!" }
  else { "Huh?! Speak up, sonny!" }
}

Squeezed 18 characters from Joey's suggestion.

BTW, 'Learn to Program' was the first programming book I ever read cover to cover.

\$\endgroup\$
2
  • \$\begingroup\$ You might want to take a look here: codegolf.stackexchange.com/questions/191/… \$\endgroup\$
    – Joey
    Commented Feb 10, 2011 at 13:00
  • \$\begingroup\$ You can get this down to 120 by squishing the first if... into the conditional check of the for() like so: for(;($j=read-host)-cne"BYE"){if($j-ceq$j.ToUpper()){... Also, the spec says 1930-1950. \$\endgroup\$
    – SpellingD
    Commented Sep 26, 2012 at 18:02
3
\$\begingroup\$

C# - 234 Chars

using System;class P{static void Main(){for(;;){var s=Console.ReadLine();if(s!=s.ToUpper()){Console.WriteLine("Huh?! Speak up, sonny!");continue;}if(s=="BYE")break;Console.WriteLine("No, not since 19{0}!",new Random().Next(30,51));}}}

More readable:

using System;
class P
{
    static void Main()
    {
        for(;;)
        {
            var s=Console.ReadLine();
            if(s!=s.ToUpper())
            {
                Console.WriteLine("Huh?! Speak up, sonny!");
                continue;
            }
            if(s=="BYE")
                break;
            Console.WriteLine("No, not since 19{0}!",new Random().Next(30,51));
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Ah missed a few simple ones in mine, and made some silly mistakes. Nice +1 \$\endgroup\$ Commented Feb 4, 2011 at 9:57
3
\$\begingroup\$

Befunge - 27x6 = 162 characters

> ~:0`  #v _            vv<
         >:"a"`!#v  _:"z"`|
^                <       <
v"Huh?! Speak up, sonny!"0<
v"No, not since 1938!"0 <
>:# #, _@

EDIT: Completely missed the "BYE" part. New version coming soon.

EDIT 2: Actually, that makes it a bit too complex for my meager Befunge skills. I might try again later, but I can't think of any simple way to implement it at the moment.

\$\endgroup\$
3
\$\begingroup\$

D: 246 Characters

import std.random,std.stdio,std.string;void main(){auto r=rndGen();for(;;){auto t=strip(readln());if(t=="BYE")break;if(t.toupper()==t)writefln("No, not since %s!",{r.popFront();return r.front%20+1930;}());else writeln("Huh?! Speak up, sonny!");}}

More Legibly:

import std.random, std.stdio, std.string;

void main()
{
    auto r = rndGen();

    for(;;)
    {
        auto t = strip(readln());

        if(t == "BYE")
            break;

        if(t.toupper() == t)
            writefln("No, not since %s!", {r.popFront(); return r.front % 20 + 1930;}());
        else
            writeln("Huh?! Speak up, sonny!");
    }
}
\$\endgroup\$
3
\$\begingroup\$

javascript, 142 characters, 29 of them perform random year

n='a'; while((/[a-z]/.test(n)?r="HUH?! SPEAK UP, SONNY!":n=="BYE"?r='':r="NO, NOT SINCE "+Math.floor(Math.random()*21+1930))!=''){n=prompt(r)}
\$\endgroup\$
3
\$\begingroup\$

Windows PowerShell, 121 117

Due to the nature of the task this looks pretty much identical to Ty Auvil's solution, although it was written independently:

for(;($j=read-host)-cne'BYE'){if($j-cmatch'[a-z]'){'Huh?! Speak up, sonny!'}else{"No, not since 19$(30..50|random)"}}

Thanks to SpellingD for the suggestion,

\$\endgroup\$
1
  • \$\begingroup\$ I like your regex matching, but the switch statement is way bulky. Along with the suggestion I gave Ty, you could reduce your character count to 117 by using regex with an if instead like this: for(;($j=read-host)-cne'BYE'){if($j-cmatch'[a-z]'){'Huh?! Speak up, sonny!'}else{"No, not since 19$(30..50|random)"}} \$\endgroup\$
    – SpellingD
    Commented Sep 26, 2012 at 18:29
3
\$\begingroup\$

Awk: 97 95 characters

Thanks to

/^BYE$/{exit}$0=toupper($0)==$0?"NO, NOT SINCE "int(rand()*21+1930)"!":"HUH?! SPEAK UP, SONNY!"

Try it online!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ 95 hytes \$\endgroup\$
    – user100411
    Commented May 9, 2021 at 10:01
  • \$\begingroup\$ Doh! How I didn't thought to? Thank you, @tailsparkrabbitear. \$\endgroup\$
    – manatwork
    Commented May 9, 2021 at 13:07
  • \$\begingroup\$ heh... before reading this I came up with /^BYE$/{exit}$0=toupper($0)!=$0?"Huh?! Speak up, sonny!":"No, not since "1930+systime()%21"!" which is almost identical... Using 1930+systime()%21 is a little shorter. \$\endgroup\$
    – cnamejj
    Commented May 10, 2021 at 2:29
  • \$\begingroup\$ Yes, but that would make it Gawk only. Currently works with any Awk. \$\endgroup\$
    – manatwork
    Commented May 10, 2021 at 5:25
2
\$\begingroup\$

Haskell (189)

import Random
import Char
main=getLine>>=g
g"BYE"=return""
g s|s/=map toUpper s=putStrLn"HUH?! SPEAK UP SONNY!">>main|4>2=randomRIO(30,50::Int)>>=putStrLn.("NO, NOT SINCE 19"++).show>>main

The weird thing is, Haskell code is usually a lot shorter than comparable C code when writing a 'serious' program.

\$\endgroup\$
1
  • \$\begingroup\$ You can avoid importing Char by using any(`elem`['a'..'z'])s to test for lower case letters. \$\endgroup\$
    – hammar
    Commented Oct 22, 2011 at 3:13
2
\$\begingroup\$

APL (76)

 {'BYE'≢A←⍞:∇⎕←{∧/⍵∊⎕A:'No, not since ',⍕1938+?20⋄'Huh?! Speak up sonny!'}A}⍬
\$\endgroup\$
1
\$\begingroup\$

C# - 345 Chars

using System;class Program{static void Main(){for(;;){if(!t(Console.ReadLine()))break;}}static bool t(string s){bool b=false;if(s=="BYE")return b;int i=0;for(;i<s.Length;i++){b=(s[i]>65&&s[i]<90)?true:false;if(!b)break;}if(b) p("NO, NOT SINCE 1938!");else p("HUH?! SPEAK UP, SONNY!");return true;}static void p(string s){Console.WriteLine(s);}}

Damn verbose language ... :-)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can call the class just P. And this doesn't detect uppercase correctly. I can shout at it and it still cannot hear me. You can shorten the main method to while(t(Console.ReadLine()));. You can use using C=System.Console; at the start to shorten access to ReadLine() and WriteLine() to C.ReadLine() and C.WriteLine(). \$\endgroup\$
    – Joey
    Commented Feb 9, 2011 at 19:15
1
\$\begingroup\$

C# - 196 chars (but leaky)

using System;class P{static void Main(){var s=Console.ReadLine();if(s!="BYE"){Console.Write((s==s.ToUpper()?"No, not since 19"+new Random().Next(30, 51):"Huh?! Speak up, sonny")+"!\n");Main();}}}

That's @Richard's (leaky) answer with two parens (see below) and a \n added in there to get the EOL in both cases. Otherwise the " + " is just wasted space.

Formatted

using System;
class P
{
    static void Main() { 
        var s = Console.ReadLine(); 
        if (s != "BYE") { 
            Console.Write((
                s == s.ToUpper() ? 
                "No, not since 19" + new Random().Next(30, 51) : 
                "Huh?! Speak up, sonny"
                ) + "!\n");
            Main(); 
        } 
    }
}

UPDATE: to clarify my comment about the parens being needed, here's what i get without the parens (i.e. with @Richard's original solution):

without parens

And with the parens:

with parens

Neither of these use my additional \n though.

\$\endgroup\$
1
  • \$\begingroup\$ It's 195 here, only. Did you count an unnecessary line break at the end? \$\endgroup\$
    – Joey
    Commented Feb 12, 2011 at 12:36
1
\$\begingroup\$

Bash: 136 128 characters

while read s
do
[[ $s = BYE ]]&&break
[[ ${s^^} = $s ]]&&echo NO, NOT SINCE $[RANDOM%21+1930]!||echo HUH?! SPEAK UP, SONNY!
done

Limited alternative: 132 123 characters

f(){
read s
[[ $s = BYE ]]||{
[[ ${s^^} = $s ]]&&echo NO, NOT SINCE $[RANDOM%21+1930]!||echo HUH?! SPEAK UP, SONNY!
f
}
}
f

You can talk to a deaf infinitely, but the conversation with this later code is limited by the call stack. (In my test it gets terminated after 4989 calls.)

\$\endgroup\$
1
\$\begingroup\$

Javascript - 133 131 130 128 127 121 chars

golfed version of www0z0ks solution

g='';while((i=prompt(g))!='BYE'){/[a-z]/.test(i)?g='Huh?! Speak up, sonny!':g='No, not since '+Math.floor(Math.random()*21+1930)+'!'}

g='';while((i=prompt(g))!='BYE'){g=/[a-z]/.test(i)?'Huh?! Speak up, sonny!':'No, not since '+Math.floor(Math.random()*21+1930)+'!'}

g='';while((i=prompt(g))!='BYE'){g=/[a-z]/.test(i)?'Huh?! Speak up, sonny!':'No, not since '+Math.ceil(Math.random()*21+1929)+'!'}

for(g='';(i=prompt(g))!='BYE';g=/[a-z]/.test(i)?'Huh?! Speak up, sonny!':'No, not since '+Math.ceil(Math.random()*21+1929)+'!');

for(g='';(i=prompt(g))!='BYE';g=/[a-z]/.test(i)?'Huh?! Speak up, sonny!':'No, not since '+parseInt(Math.random()*21+1930)+'!');

for(g='';(i=prompt(g))!='BYE';g=/[a-z]/.test(i)?'Huh?! Speak up, sonny!':'No, not since '+(Math.random()*21+1930|0)+'!');

Edit: Saved another six chars with this great tip

\$\endgroup\$
4
  • \$\begingroup\$ Write the ternary operator as g=/[a-z]/.test(i)?'Huh?!...':'No...' and you spare 2 characters. \$\endgroup\$
    – manatwork
    Commented Sep 26, 2012 at 13:43
  • \$\begingroup\$ Edited, thanks for the pointer. \$\endgroup\$
    – codeporn
    Commented Sep 27, 2012 at 14:33
  • 1
    \$\begingroup\$ 1 more character I found: Math.ceil() is shorter than Math.floor(). Just change the base year to keep the interval unchanged: Math.ceil(Math.random()*21+1929). \$\endgroup\$
    – manatwork
    Commented Sep 27, 2012 at 15:21
  • \$\begingroup\$ Great, +1! I saved another two chars with changing the while to a for loop. \$\endgroup\$
    – codeporn
    Commented Sep 28, 2012 at 8:15
1
\$\begingroup\$

Jelly, 54 bytes

21X+⁽¤Ḷ“ЀÑƊʂ!ṡxẈ»,K”!
ɠ¢“®VẹQƝɗi³ỤwṖoỴ»ŒuƑ?ṄßƊḟ⁻?“BYE

Try it online!

Jelly, 49 bytes

21X+⁽¤Ḷ“ЀÑƊʂ!ṡxẈ»,K”!µ“®VẹQƝɗi³ỤwṖoỴ»ŒuƑ?ḟ⁻?“BYE

Try it online!

Jelly, 51 bytes

21X+⁽¤Ḷ“ЀÑƊʂ!ṡxẈ»,K;”!µ“®VẹQƝɗi³ỤwṖoỴ»ŒuƑ?ḟ⁻?“BYE”

Try it online!


The first one is perhaps the most faithful interpretation of the question. It is a full program which reads input line by line from STDIN and acts on it line by line, terminating either when it reaches "BYE" or the end of STDIN (at which point it exits with an error).

The second is a full program which takes a single string as input (e.g. Hello, HELLO, BYE, etc.) and outputs the corresponding output (an empty output for BYE)

This third is a function which can be repeatedly called with arguments in a similar version to the second, but each argument is independent - calling it with BYE will not prevent you calling it again.

All three output not in uppercase, reflecting the text of the question rather than the sample program provided. +5 bytes to output in all uppercase


As they all have the same basic structure, I'll only explain the first one, and leave the other two as exercises for the reader

How it works

21X+⁽¤Ḷ“ЀÑƊʂ!ṡxẈ»,K”! - Helper link. This takes no arguments, outputs a string and returns "!"
21                     - Yield 21
  X                    - Choose a random integer between 1 and 21 inclusive
    ⁽¤Ḷ                - Compressed integer: 1929
   +                   - Add to the random integer. Call that Y
       “ЀÑƊʂ!ṡxẈ»     - Compressed string: "No, not since"
                  ,    - Pair; ["No, not since", Y]
                   K   - Join the two with spaces
                    ”! - Unparseable nilad; Output the space separated string and return "!"

ɠ¢“®VẹQƝɗi³ỤwṖoỴ»ŒuƑ?ṄßƊḟ⁻?“BYE - Main link f(). Takes no arguments
ɠ                               - Read a line of STDIN, L
                          ?     - If:
                         ⁻ “BYE -   Condition: L does not equal "BYE"
                       Ɗ        -   Then:
                    ?           -     If:
                   Ƒ            -       Condition: L does not change when:
                 Œu             -         Converted to uppercase
 ¢                              -       Then: Call the helper link, yielding "!"
  “®VẹQƝɗi³ỤwṖoỴ»               -       Else: Compressed string "Huh?! Speak up, sonny!"
                     Ṅ          -     Print with a trailing newline
                      ß         -     Recurse, calling f() again
                        ḟ       -   Else: Return the empty string and halt execution
\$\endgroup\$
1
\$\begingroup\$

Vyxal, 57 bytes

{:‛Ṗ⊍⇧≠|aA[`No, λ† ∧Ṡ %!`»÷ḟ»21℅+%,|`Huh?! ∞⇧ up, ½Ŀny!`,

Try it Online!

Vyxal's not particularly good at English compression.

{                                # While...
 :   ≠|                          # Take an input, is it not equal to...
  ‛Ṗ⊍⇧                           # "BYE"
       aA[                       # If all uppercase
          `...`        %,        # Print "No, not since" formatted by
               »÷ḟ»21℅+          # 1929 + random(1..21)
                          `...`, # Otherwise print "Huh?! Speak up, sonny!"
\$\endgroup\$
0
\$\begingroup\$

Clojure - 160 154 Characters

(#(if(= % "BYE")%(do(if(=(.toUpperCase %)%)(prn(str"No, not since "(+ 1930(rand-int 9))"!"))(prn"Huh?! Speak up, sonny!"))(recur(read-line))))(read-line))

Working on golfing it a bit more. Suggestions welcome.

Run through REPL

\$\endgroup\$
0
\$\begingroup\$

Q, 115

{while[1;v:read0 0;$[v~"BYE";'`;v~upper v;-1"No, not since ",/:(($)1?1930+(!)20),'"!";-1"Huh?! Speak up, sonny!"]]}

usage

q){while[1;v:read0 0;$[v~"BYE";'`;v~upper v;-1"No, not since ",/:(($)1?1930+(!)20),'"!";-1"Huh?! Speak up, sonny!"]]}`
Hi
Huh?! Speak up, sonny!
Hello
Huh?! Speak up, sonny!
HELLO!
No, not since 1938!
Goodbye Grandma
Huh?! Speak up, sonny!
BYE
'
\$\endgroup\$

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.