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

Dear Monks,

This subroutine works fine for cases where 'n' is greater than one. I had to create an additional sub called UnPackOne for the unique case of 1 input. When I call this routine with one parameter, the routine always returns '1' and not the array (which it did create correctly). I think it forces the context to scalar!

# our $Zeropack = "\0" x 4; # our $MaxNum32bit = 2 ** 32; # Use: ## n - number of 64bit pack +ed numbers in string # my @Numbers = UnPack( n, $stringpack ); ## string input, array outp +ut sub UnPack { my $todo = shift; my $input = shift; my @output = (); my $arno = 0; while( $arno < $todo ) { my $pack = substr( $input, $arno * 8, 8 ); if ( substr($pack,0,4) eq $Zeropack ) { $output[$arno] = unpack("N", substr( $pack, 4, 4 ) ); } else { my ( $upper, $lower ) = unpack( "N N", $pack ); if ( ! defined $upper ) { $upper = 0; } if ( ! defined $lower ) { $lower = 0; } $output[$arno] = ( $upper * $MaxNum32bit ) + $lower ; } $arno++ } return ( @output ); }
Is there a way to force the return value to always be treated as a array list?

Thank you

"Well done is better than well said." - Benjamin Franklin

Replies are listed 'Best First'.
Re: Force 'sub' return to be treated in list context?
by GrandFather (Saint) on Apr 04, 2012 at 10:57 UTC

    You haven't shown us a test case which fails so I'm guessing, but if you write:

    my $thing = UnPack (1, $stringpack);

    $thing will get a count instead of the "first" element of the returned array because UnPack is called in scalar context. Consider:

    use strict; use warnings; my @single = (1); my @many = (1 .. 3); for my $ref (\@single, \@many) { my $count = listy(@$ref); my @elements = listy(@$ref); my ($firstElement) = listy(@$ref); print "Count $count, first: $firstElement, elements: @elements\n"; } sub listy { return @_; }

    Prints:

    Count 1, first: 1, elements: 1 Count 3, first: 1, elements: 1 2 3

    If you write:

    my ($thing) = UnPack (1, $stringpack);

    I suspect your problem will be resolved. It's not what you return, it's the context that is important and the () provides a list context.

    True laziness is hard work

      GrandFather,

      I did show how to use the UnPack, but the following should demonstrate:

      # Use: ## n - number of 64bit pack +ed numbers in string # my @Numbers = UnPack( n, $stringpack ); ## string input, array outp +ut my $pack = pack("N N", 0, 99 ); my $Numbers = UnPack( 1, $pack ); say $Numbers; ( $Numbers ) = UnPack( 1, $pack ); say $Numbers; my @Numbers = UnPack( 1, $pack ); say $Numbers[0]; ( @Numbers ) = UnPack( 1, $pack ); say $Numbers[0]; __END__ result: 1 99 99 99

      On running this code, I could not reproduce my original statement, so I can only assume that I typed "$Numbers" instead of "@Numbers" when I wrote the test cases for 'sub UnPack'. But if I had put parenthesis around the scalar, I still would have gotten what I wanted ( list context ).

      Thank you

      "Well done is better than well said." - Benjamin Franklin

Re: Force 'sub' return to be treated in list context?
by moritz (Cardinal) on Apr 04, 2012 at 09:45 UTC

      moritz,

      I have never used 'wantarray', but I will now. I looked all over for something like 'scalar', but for lists. I like the comment in the Camel book, '...should really have been named "wantlist"...'

      I'm going to put it in the code, so I will remember how to use it, but the sub 'UnPackOne' is faster for 1 element. I still wanted to know how to return a list of one.

      Thank you

      "Well done is better than well said." - Benjamin Franklin

Re: Force 'sub' return to be treated in list context?
by jwkrahn (Abbot) on Apr 04, 2012 at 17:45 UTC
    # our $Zeropack = "\0" x 4; # our $MaxNum32bit = 2 ** 32; # Use: ## n - number of 64bit pack +ed numbers in string # my @Numbers = UnPack( n, $stringpack ); ## string input, array outp +ut sub UnPack { my $todo = shift; my $input = shift; my @output = (); my $arno = 0; while( $arno < $todo ) { my $pack = substr( $input, $arno * 8, 8 ); if ( substr($pack,0,4) eq $Zeropack ) { $output[$arno] = unpack("N", substr( $pack, 4, 4 ) ); } else { my ( $upper, $lower ) = unpack( "N N", $pack ); if ( ! defined $upper ) { $upper = 0; } if ( ! defined $lower ) { $lower = 0; } $output[$arno] = ( $upper * $MaxNum32bit ) + $lower ; } $arno++ } return ( @output ); }

    You don't verify that the input is correct and you are doing too much work for such a simple task.    I would do something like this:

    # use constant Max_Num_32_bit => 2 ** 32; # Use: ## n - number of 64bit pack +ed numbers in string # my @Numbers = UnPack( n, $stringpack ); ## string input, array outp +ut sub UnPack { my ( $todo, $input ) = @_; if ( @_ != 2 || length $input < $todo * 8 ) { warn "Invalid input for UnPack()\n"; return; } my @output; for my $arno ( 0 .. $todo - 1 ) { my $pack = substr $input, $arno * 8, 8; my ( $upper, $lower ) = unpack 'N N', $pack; push @output, $upper * Max_Num_32_bit + $lower ; } return @output; }

      jwkrahn,

      I benchmarked your code against mine, and for performance, my original 'UnPack' is faster on my hardware with debian linux and perl5.12.2.

      case1 is mine, case2 is yours. I took out your test on the input since the input is CRC verified before calling 'UnPack'. I then modified my code to use 'for' instead of 'while' and using 'push' and now the results were only 2% faster.

      This routine is called about 14 million times on a run of 100,000 inputs. The first time I profiled the program that this is part of, more than 50% of the time was spent in pack/unpack. I have done a lot of tweaking to get the performance. A year ago the 1st 100K run, I could only get 50 operations per second. Now on a 1,000K run, I get 1700 operations per second and pack/unpack is less than 10% of run time.

      Benchmark: timing 500000 iterations of case1, case2... case1: 3 wallclock secs ( 3.76 usr + 0.00 sys = 3.76 CPU) @ 13 +2978.72/s (n=500000) case2: 4 wallclock secs ( 4.11 usr + 0.00 sys = 4.11 CPU) @ 12 +1654.50/s (n=500000) Rate case2 case1 case2 119332/s -- -9% case1 131234/s 10% --

      This code was necessary to allow disk positioning on a 32 bit OS of 2**53 and 2**63 on 64 bit OS and still be network neutral. That also means I don't have to consider big/little endian problems. None of this would be necessary if pack/unpack had network neutral 64 bit integer that worked on both 32bit and 64bit hardware/OS.

      Thank you

      "Well done is better than well said." - Benjamin Franklin