http://qs321.pair.com?node_id=976464

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

Hello, I have started to work through the Intermediate Perl book after the Learning Perl book. I really enjoyed the Learning Perl book, it was pretty clear and readable. The Intermediate seems less clear and I am already stuck in the second chapter on a grep example:

my @odd_digit_sum = grep digit_sum_is_odd($_), @input numbers; sub digit_sum_is_odd { my $input = shift; my @digits = split //, $input; my $sum; $sum += $_ for @digits; return $sum % 2; }

The book gives @input_numbers = (1, 2, 4, 8, 16, 32, 64), and says that the @odd_digit_sum resulting from the grep code will give 1, 16, and 32. I must be an idiot, because I do not understand this example( and the book really does not explain it). If grep places each value of @input_numbers into $_ one at a time, how does it ever sum anything??

I thought maybe it calls the subroutine with the entire list (even though this is not how grep is supposed to work), but even then, the answer of 1, 16 and 32 does not make sense to me. I know grep will give the answers when sum % 2 is not zero, but if you start with 1, then add 2, you get 3 which gives non-zero result, so grep should give 2 also. Still do not understand how it sums anything, given that $_ gets a single value one at a time?! Really confused, any help would be appreciated!! Thanks!

Replies are listed 'Best First'.
Re: do not understand grep example
by BrowserUk (Patriarch) on Jun 15, 2012 at 17:38 UTC

    The grep line my @odd_digit_sum = grep digit_sum_is_odd($_), @input numbers;

    passes each number into the subroutine using $_. Within the subroutine that value is assigned to $input:

    my $input = shift;

    The digits of each number are then split into the array @digits

    my @digits = split //, $input;

    At this point, if $input is 32; then @digits contains: ( '3', '2' ).

    Those digits are then summed

    $sum += $_ for @digits;

    giving 5, and then tested to see if the result is odd:

    return $sum % 2;

    Which in the case of 32 is true, so true is returned to grep and grep allows that input (32) through to the results array.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

    The start of some sanity?

      Thank you all for your responses, the Monastery has not failed me yet!

      BrowserUk, your response provided me with the answer. I realized that my issue was not with grep, but rather with split. I did not realize that split using null (split //) would split by character (e.g. "32" would become 3, 2).

      Now I see when the summing takes place. The book glossed over this, but it would have been nice if it had just noted that to jog my memory, which is not so great anymore.

      Thank you!
Re: do not understand grep example
by moritz (Cardinal) on Jun 15, 2012 at 17:29 UTC
    thought maybe it calls the subroutine with the entire list (even though this is not how grep is supposed to work),

    No, it does not. It calls the subroutine for every value in the list, and only returns if the subroutine returns a true value.

    So you could also write

    my @odd_digit_sum = grep digit_sum_is_odd($_), @input numbers;

    As

    my @odd_digit_sum; for (@input) { if (digit_sum_is_odd($_)) { push @odd_digit_sum, $_; } }

    and it would return the same result. That's what grep does: it goes through the list, calls the function for every value, and collect those values for which the function returned something true.

Re: do not understand grep example
by tobyink (Canon) on Jun 15, 2012 at 18:45 UTC

    Forget about the grep for a while and just get straight in your head how the digit_sum_is_odd function works and what it does.

    use Test::More tests => 14; sub digit_sum_is_odd { my $input = shift; my @digits = split //, $input; my $sum; $sum += $_ for @digits; return $sum % 2; } ok digit_sum_is_odd(1); ok not digit_sum_is_odd(2); ok digit_sum_is_odd(3); ok digit_sum_is_odd(9); ok digit_sum_is_odd(10); ok not digit_sum_is_odd(11); ok digit_sum_is_odd(12); ok not digit_sum_is_odd(13); ok digit_sum_is_odd(1000000); ok not digit_sum_is_odd('pony'); ok digit_sum_is_odd('pony1'); ok digit_sum_is_odd(3, 4); ok digit_sum_is_odd(3, 5); ok digit_sum_is_odd(3, 'chimpanzee', 'monkey', 'baboon');
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: do not understand grep example
by davies (Prior) on Jun 15, 2012 at 17:41 UTC

    Grep is not something I understand perfectly, so this answer may be corrected by the more knowledgeable, but I'll try anyway. Grep takes two arguments, the condition and the list. The list is the array. The condition is the subroutine that you provide. So each element in turn becomes $_ (the default variable), but before being passed to grep, the sub is evaluated using $_.

    Sequentially, 1 becomes $_. This is passed through the sub which returns 1 (true) without changing $_. Grep therefore includes 1 (the value of $_, not true) in its output. Then it passes 2. The sub returns 0 (false) and 2 is ignored by grep. Ditto 4 and 8. When $_ becomes 16, the sub will return 1 (true) again without changing $_ and 16 will be pushed into the output array.

    The important thing to understand is the working of $_. It is the value that grep is currently considering and it is also the value passed to the sub as the test parameter that grep will use to decide if $_ should be in the output. Note the line in the sub my $input = shift;. This makes a copy of the value of $_ for internal use in the sub, thus ensuring that $_ does not get changed.

    Regards,

    John Davies