jerrygarciuh has asked for the wisdom of the Perl Monks concerning the following question:

I am interested in being able to condense the If/Else statement in this sub into a single line which accepts and chomps only digits and calls itself if it gets anything else. Any help?
TIA
jg
sub input { my $arrayref=shift; print "\nSo far you've guessed @$arrayref." if $arrayref; print "\nWhat is your guess?\n"; chomp (my $in=<STDIN>); if ($in=~ /\d/) { $in; } else { print "$in was not a number!"; input(); } }
_____________________________________________________
Ain't no time to hate! Barely time to wait! ~RH

Replies are listed 'Best First'.
Re: Can this If/Else be condensed into a 1 liner with a trailing If?
by pjf (Curate) on Oct 02, 2001 at 07:57 UTC
    Oh good, I'm glad you posted this. I had to take a phone-call while it was being discussed on the chatterbox, and when I came back the conversation had cleared.

    Firstly your regexp will match anything which has a digit it in somewhere. This might not be what you want. Perhaps something like /^\s*(\d+)\s*$/ would be better, to require a sequence of digits with possible whitespace on either side. This leaves your number in $1 afterwards.

    Secondly, think twice about having your input function call your input function. You could end up with a very large call stack here, killing performance and chewing memory.

    I would suggest you use a loop like this:

    while (defined($_ = <STDIN>)) { print "Numbers only please" and next unless (/^\s*(\d+)\s*$/); return $1; } die "urk! Out of input.\n";
    This keeps reading from STDIN until we get a number (in which case we return it), or we run out of input. Plus there's no recusion involved, which is much better than calling ourselves repeatedly.

    It's not a single line, but it probably does what you want, and is shorter than the original code.

    Cheers,
    Paul

      Shorter still, but (IMHO) just as readable ...

      print "Numbers only please" until <STDIN> =~ /^(\d+)$/; return $1;

          --k.


      Isn't exactly the same as: in perl 5.005 and above?

      I remember a time when they *weren't* the same, but I'm pretty sure that they are now.

      -Blake

        Well spotted, and quite correct. When dealing with numbers I tend to step to a higher level of paranoia, simply because zero is a number, but it's not true. This paranoia occasionally wears off onto the code around it. ;)

        Cheers,
        Paul

Re: Can this If/Else be condensed into a 1 liner with a trailing If?
by lestrrat (Deacon) on Oct 02, 2001 at 07:59 UTC

    Ah! potential inifinite recursion! bad! bad! Use a loop, not a recursion!

    I was going to write an example code, but I don't understand what you want to do... you might want to clarify the following:

    • What do you mean by "chomps only digits"?
    • What is the scalar "$in" doing in the if{ } clause??
    • Where are you getting/setting your "$arrayref"?
      He could always have said: goto &input; and avoided the recursion entirely. :)

      For the humorless, the smiley means I'm kidding.

Re: Can this If/Else be condensed into a 1 liner with a trailing If?
by George_Sherston (Vicar) on Oct 02, 2001 at 16:10 UTC
    I think I must be missing something about the if bit - it doesn't seem to do anything. If that's right, then you could get the same result with
    print "$in was not a number!"; input() unless $in =~ /\d/;
    which does get it on one line, though not, I agree, with a trailing if :). On the other hand if the line
    $in;
    is a line you need (perhaps an idiom I don't know - if so, what does it do? - in any event it has a certain classically simplicity about it) - then to get it on one line you could use the conditional operator, although there might be some pros and cons here I don't know about:
    $in =~ /\d/ ? $in : print "$in was not a number!";
    That doesn't let you call input(), but that's ok if you agree with lesstrat that it might get a bit ropey if you call input() from within input().

    On the broader question of how to detect numbers, I find that what I usually want to isolate is everything that isn't not a number, so my favourite idiom is
    $in !~ /\D/
    But this question is much more complicated than one would think, as these and threads explain.

    § George Sherston
      the $in...

      As you may or may not know, perl will return the last value in a subroutine it sees... so

      sub foo { 'foo'; }
      will return foo even without an explicit return 'foo';
      so in a case like the following
      print(foo(),"\n"); sub foo { if(1) { 'foo'; } else { 'bar'; } }
      foo will be printed, since it is the last real value perl saw in the subroutine, everything else was basically blank lines... BUT! one must be careful in this situation because if you ever come back and put code in after the if statement like
      print(foo(),"\n"); sub foo { if(1) { 'foo'; } else { 'bar'; } print STDERR 'fooed'; }
      then you will get 1, the return value of a successful print. most people use an explicit return unless the value they are returning is the last line of the sub, since it is then much harder to miss.

                      - Ant
                      - Some of my best work - Fish Dinner

        Hmm. I thought it might be something like that, but now instead of a nebulous feeling that it must be something to do with returning values I actually know how it works! Very educational - thanks for the explanation. My subroutines will be grateful too.

        So (just getting this straight, hoping my thought processes won't be too dull for others) in jerrygarciuh's case, if he replaces the if... else with
        print "$in was not a number!"; input() unless $in =~ /\d/;
        then the subroutine will return the number of characters chomped from $in, rather than $in itself. So in order to reduce the if... else loop to a single line, one wd have to do something like
        sub input { my $arrayref=shift; print "\nSo far you've guessed @$arrayref." if $arrayref; print "\nWhat is your guess?\n"; chomp <STDIN>; my $in=<STDIN>; print "$in wasn't a number!";input() unless $in =~ /\d/; }
        Would that work? Is it nicer? Have I learnt something from this exchange? (The answer to at least one of these questions is "yes".)

        § George Sherston
      Hi,

      I personally love using the conditional operator. You just have to be careful with it. For example:

      #! /usr/local/bin/perl -w use strict; $a = 1; $b = 2; (1) ? $a = 3 : $b = 4; print "1) a = $a, b = $b\n"; (1) ? ($a = 3) : ($b = 4); print "2) a = $a, b = $b\n";
      displays:
      1) a = 4, b = 2 2) a = 3, b = 2
      Which may or may not be what you expect.

      Good luck!
      {NULE} PS I'm having issues with Opera again, please forgive me if I screw this post up (again.) Time for a new web-browser *sigh*.

Re: Can this If/Else be condensed into a 1 liner with a trailing If?
by Caillte (Friar) on Oct 02, 2001 at 15:30 UTC

    How about....

    while($x=<STDIN> and $x !~ /^\s*\d+\s*$/) { print "Enter a number\n" }

    $japh->{'Caillte'} = $me;