http://www.perlmonks.org?node_id=943428

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

How does @filesize_names get altered by the map when the conditional in make_singular is true? When the conditional is false, @filesize_names is not altered by the map outside make_singular.

#!/usr/bin/perl use strict; use warnings FATAL => qw( all ); use Data::Dumper; use List::MoreUtils qw(firstidx); use lib 'lib'; use Base::Nifty qw(pretty_number); local $\ = "\n"; my @filesize_names = qw(bit nibble byte kilobyte megabyte gigabyte ter +abyte petabyte exabyte zettabyte yottabyte); # I put the these sizes here just for my information. my %little_filesizes; $little_filesizes{bit} = 1; $little_filesizes{nibble} = $little_filesizes{bit} * 4; $little_filesizes{byte} = $little_filesizes{bit} * 8; # I never know when I'll want a random file size. sub random_filesize { return $filesize_names[rand @filesize_names] } sub make_singular { my $word = shift; $word =~ s/s$//; my @short_sizes = map(s/^(\w)\w{1,}$/$1b/,@filesize_names); if (grep(/\L$word\E/,@short_sizes)) { $word = $filesize_names[firstidx { $_ eq lc $word} @short_sizes]; } return lc $word; } # from tye: my %hash; @hash{@sizes} = 0..$#sizes; # from MidLifeXis: $result = $original * $units{$inmultiplier} / $unit +s{$outmultiplier} sub convert_filesize { my %opt = @_; # I took out bits and nibbles just to keep me sane. my @filesizes = grep($_ =~ /byte/,@filesize_names); my $from = firstidx { $_ eq make_singular($opt{from}) } @filesizes; my $to = firstidx { $_ eq make_singular($opt{to}) } @filesizes; my $dec = $opt{decimals} ? $opt{decimals} : 0; my $base = $opt{base} ? $opt{base} : 1024; my ($diff,$converted); if ( $from > $to ) { $diff = $from - $to; $converted = $opt{size} * ($base ** $diff); } elsif ( $to > $from ) { $diff = $to - $from; $converted = $opt{size} / ($base ** $diff); } else { $converted = $opt{size}; } my $org_filesize = pretty_number($dec,$opt{size}); my $new_filesize = pretty_number($dec,$converted); return "$org_filesize $opt{from} ($from) is $new_filesize $opt{to} ( +$to)"; } print random_filesize; my $conversion = convert_filesize( size => 10000000, from => 'bytes', to => 'megabytes', ); print $conversion;

I just tried...

sub make_singular { my $word = shift; $word =~ s/s$//; my @in_sub_filesize_names = @filesize_names; # added this line, the +output is still wrong. my @short_sizes = map(s/^(\w)\w{1,}$/$1b/,@in_sub_filesize_names); if (grep(/\L$word\E/,@short_sizes)) { $word = $filesize_names[firstidx { $_ eq lc $word} @short_sizes]; } return lc $word; }
Have a cookie and a very nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: Global array afftected by map inside of a subroutine
by chromatic (Archbishop) on Dec 13, 2011 at 22:34 UTC

    Are you using Perl 5.14? If so, what happens when you use the non-destructive replacement flag /r on the regex?

    Remember that because map operates on $_, it can update array elements in place. Remember also that map returns the return value of its last expression, which is not always $_.


    Improve your skills with Modern Perl: the free book.

      I'm using Strawberry Perl 5.10 for Windows XP.

      Have a cookie and a very nice day!
      Lady Aleena

        Then you need something like:

        my @short_sizes = map { my $item = $_; $item =~ s/^(\w)\w{1,}$/$1b/; $item } @in_sub_filesize_names;

        Improve your skills with Modern Perl: the free book.

        In map, grep, and in "for" loops, $_ is an alias to the current item, so this would work also:
        my @short_sizes = @in_sub_filesize_names; s/^(\w)\w{1,}$/$1b/ for @short_sizes;
Re: Global array afftected by map inside of a subroutine
by ikegami (Patriarch) on Dec 14, 2011 at 06:43 UTC

    Your map is returning the wrong value, and its clobbering its arguments by changing $_.

    List::MoreUtils's apply and Algorithm::Loops's Filter have the interface you are expecting from map.

      ikegami...List::MoreUtils's apply did exactly what I needed for the array. Thank you! Now all I need to figure out is why my grep is breaking.

      I isolated the code that I am working on, but grep is grepping everything. I've never had this problem with grep before.

      my @filesize_names = qw(bit nibble byte kilobyte megabyte gigabyte ter +abyte petabyte exabyte zettabyte yottabyte); my $word = 'fubar'; $word =~ s/s$//; my @short_sizes = apply {$_ =~ s/^(\w)\w{1,}$/$1b/} @filesize_names; print Dumper(\@filesize_names); print Dumper(\@short_sizes); if (grep(lc $word,@short_sizes)) { # even 'fubar' greps print "$word grepped!"; $word = $filesize_names[firstidx { $_ eq lc $word } @short_sizes]; } print $word;
      Have a cookie and a very nice day!
      Lady Aleena
        grep is grepping everything. I've never had this problem with grep before.

        You are using:

        grep( lc $word, @short_sizes )

        grep passes through any value where the expression (first argument) is true.

        Your grep expression is lc $word. If $word has any value except undef, '' or 0, then lc $word will also have a value, and therefore be true, and everything in @short_sizes will pass through.

        Since if( grep( ...,... ) ) { places grep in a scalar context, the result will be the number of items passed through, which will be the same as the number of elements in @short_sizes. So, unless it is empty, the if condition will be true.

        Effectively, if (grep(lc $word,@short_sizes)) {

        is the same as:if( defined( $word ) && @short_sizes > 0 ) {

        which almost certainly isn't what you intend.


        So, what are you trying to achieve with that construct?

        If you are trying to check if any of the values in @short_sizes matches the lower cased value of $word, the you would need:

        if( grep( $_ eq lc $word, @short_sizes ) ) {

        But that's just my guess as to your intent.


        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?

        Based on your code sample, might I suggest a hash lookup table, eg:

        # Untested my @filesize_names = qw(kilobyte megabyte gigabyte terabyte petabyte e +xabyte zettabyte yottabyte); my %short_sizes = map { m/^(\w)/; $1 . 'b' => $_ } @filesize_names; my $word = 'kb'; if ( exists $short_sizes{lc $word} ) { say $short_sizes{lc $word}; }
Re: Global array afftected by map inside of a subroutine
by Lady_Aleena (Priest) on Dec 14, 2011 at 23:12 UTC

    Now that I have it working as I had hoped, here is the completed code (with POD) if you are interested.

    #!/usr/bin/perl use strict; use warnings FATAL => qw( all ); use Data::Dumper; use List::MoreUtils qw(firstidx apply); use lib 'lib'; use Base::Nifty qw(pretty_number); =head1 Filesize converter This script converts one filesize to another filesize like gigabytes t +o kilobytes and the reverse. There is also a random filesize generator included for fun. =head2 Author(s) Lady_Aleena with lots of help from PerlMonks. =head2 Usage To use this script, please use the following. convert_filesize( size => 10101101, from => 'megabytes', to => 'kb', decimals => 2, base => 1000, ); print $conversion; size is the original size you want to convert and is required. from is the filesize name you want to convert from and is required. to is the filesize name you want to convert to and is required. decimals is the how many decimals you want returned. Conversions from +smaller to larger can lead to a lot of decimals places. The default i +s 0. base is how many of a smaller is in a larger. Some use 1024 as the bas +e while others use 1000. Default is 1024. =head3 from and to For the from and to fields, you do not have to worry about case. byte(s) or bb kilobyte(s) or kb megabyte(s) or mb gigabyte(s) or gb terabyte(s) or tb petabyte(s) or pb exabyte(s) or eb zettabyte(s) or zb yottabyte(s) or yb =head2 Random To use the random generator if you happen to have a secret agent prote +cting a file of a size you don't feel like coming up with, you'd do.. +. my $random_filesize = random_filesize(); You could get anything from a bit to a yottabyte. =cut local $\ = "\n"; my @filesize_names = qw(bit nibble byte kilobyte megabyte gigabyte ter +abyte petabyte exabyte zettabyte yottabyte); # I put the these sizes here just for my information. my %little_filesizes; $little_filesizes{bit} = 1; $little_filesizes{nibble} = $little_filesizes{bit} * 4; $little_filesizes{byte} = $little_filesizes{bit} * 8; # I never know when I'll want a random file size. sub random_filesize { return $filesize_names[rand @filesize_names] } sub make_singular { my $word = shift; $word =~ s/s$//; my @short_sizes = apply {$_ =~ s/^(\w)\w{1,}$/$1b/} @filesize_names; if (grep(/^\L$word\E$/,@short_sizes)) { $word = $filesize_names[firstidx { $_ eq lc $word } @short_sizes]; } return $word; } # from tye: my %hash; @hash{@sizes} = 0..$#sizes; # from MidLifeXis: $result = $original * $units{$inmultiplier} / $unit +s{$outmultiplier} sub convert_filesize { my %opt = @_; # I took out bits and nibbles just to keep me sane. my @filesizes = grep($_ =~ /byte/,@filesize_names); my $from = firstidx { $_ eq make_singular($opt{from}) } @filesizes; my $to = firstidx { $_ eq make_singular($opt{to}) } @filesizes; my $dec = $opt{decimals} ? $opt{decimals} : 0; my $base = $opt{base} ? $opt{base} : 1024; my ($diff,$converted); if ( $from > $to ) { $diff = $from - $to; $converted = $opt{size} * ($base ** $diff); } elsif ( $to > $from ) { $diff = $to - $from; $converted = $opt{size} / ($base ** $diff); } else { $converted = $opt{size}; } my $org_filesize = pretty_number($dec,$opt{size}); my $new_filesize = pretty_number($dec,$converted); return "$org_filesize $opt{from} is $new_filesize $opt{to}"; } print random_filesize; my $conversion = convert_filesize( size => 10101101, from => 'megabytes', to => 'kb', decimals => 2, base => 1000, ); print $conversion;
    Have a cookie and a very nice day!
    Lady Aleena