Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: Parsing @ARGV w/ Map Function

by ELISHEVA (Prior)
on Feb 14, 2011 at 07:18 UTC ( [id://887932]=note: print w/replies, xml ) Need Help??


in reply to Parsing @ARGV w/ Map Function

I'm just wondering if there's enough flexibility to do all this.... map's a great tool and I also wanted to see just how far I could go with it when I first learned it.

Map does indeed have the flexibility you seek. That {...} block can contain nearly anything, including long if...elsif...else statements. Thus anything you can do with a sequence of map statements most likely can be done in one.

The main limitation on map is this: you can't "return" from within the {...} block. If you try, you will return from the surrounding subroutine and not just quit mapping or skip an array element. What you can do if you want to skip an array element (e.g. a bad command option) is return an empty list: (). You can also do that if you want to put the data for that particular array element into some other data structure than the hash you are building.

Your original spec would look something like this. Note the use of if..elsif to vary the result of the map based on a regular expression match. This works because map only cares about the last executed line, not the last line visually appearing in the block:

use strict; use warnings; use Data::Dumper; my $i=0; my %hARGV = map { if (/^--(\w+)=(\w+)$/) { # option => value $1 => $2; } elsif (/^--(\w+)$/) { # flag => 1 $1 => 1 } elsif (m{^[\w/]}) { # filename => fileorder $_ => $i++; } else { # bad argument - add nothing to the result hash warn "Invalid option: <$_> - options must begin with " ."-- or be a legal file name"; (); } } @ARGV; print Dumper(\%hARGV);

You could also stuff the filenames into an array so that you wouldn't have to reconstruct the array order by scanning the hash. This example also illustrates the technique of returning () when you want to do something with an array element other than place it immediately in the hash:

use strict; use warnings; use Data::Dumper; my @aFiles; # <== array instead of my $i my %hARGV = map { if (/^--(\w+)=(\w+)$/) { # option => value $1 => $2; } elsif (/^--(\w+)/) { # flag => 1 $1 => 1 } elsif (m{^[\w/]}) { push @aFiles, $_; # <== put filenames into an array (); # <== don't put anything into hash (yet) } else { # bad argument - add nothing to the result hash warn "Invalid option: <$_> - options must begin with " ."-- or be a legal file name"; (); } } @ARGV; $hARGV{files}=\@aFiles if @aFiles; # <== _now_ put the files in the ha +sh print Dumper(\%hARGV);

The main thing to worry about with map is going a little bit crazy and trying to do everything in a map (I know I did at first). Usually, if the map block gets to be more than a few lines I'll define a subroutine and call that subroutine within map, like this:

use strict; use warnings; use Data::Dumper; sub processArgs { my ($sArg, $aFiles) = @_; if ($sArg =~ /^--(\w+)=(\w+)$/) { # option => value $1 => $2; } elsif ($sArg =~ /^--(\w+)/) { # flag => 1 $1 => 1 } elsif ($sArg =~ m{^[\w/]}) { push @$aFiles, $_; (); } else { # bad argument - add nothing to the result hash warn "Invalid option: <$_> - options must begin with " ."-- or be a legal file name"; (); } } my @aFiles; my %hARGV = map { processArgs($_, \@aFiles) } @ARGV; $hARGV{files}=\@aFiles if @aFiles; print Dumper(\%hARGV);

On a final note, learning exercises aside, for real option processing, do take a look at the core module Getopt::Long. It can also convert @ARGV into a hash and goes well beyond the list of argument processing features you discussed above: collapse multiple options into arrays, auto define options (e.g. --noredirect as well as just --redirect), and much, much more.

Replies are listed 'Best First'.
Re^2: Parsing @ARGV w/ Map Function
by HalNineThousand (Beadle) on Feb 14, 2011 at 07:59 UTC

    Wow!

    While all these answers are helpful, this is the one that opened several doors for me that I wasn't even thinking about. From what I read I didn't think whole statements would fit inside map, much less just calling a subroutine.

    I played around and got things working quickly from your examples. (Before that, I was going over conditional regex abilities, and not getting too far.) Once I saw that, it was easy and an if...elsif...else block did fine for testing (but it ignored malformed arguments, this is just an experiment so far). Then I looked back and found what I came up with was pretty close to what you had come up with.

    But there is one big question I have when looking at your 2nd example. I wasn't sure if map was a loop or if it handled data in other ways. So what is the difference in whether I use map or a foreach loop with almost the same statements in it? Is there a speed difference or anything else?

    I will be looking at Getopt::Long. It's amazing the things you can miss that everyone else considers standard when you're self-taught. It's the kind of thing where I might see someone using it and say to them, "You never told me you could do that," and the response is, "You never asked." Sometimes there's so much it's hard to know what to ask about what is out there.

    Thank you, everyone, for the helpful posts. While this is quick and simple, so it might get used on some small programs, I can see Getopt::Long has much more to use for parsing arguments. My main intent was that I knew there had to be a way to do this and I wanted to see what it was and what it included that I didn't know about.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://887932]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (3)
As of 2024-03-29 06:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found