Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Producing 2 lists from a grep call

by BrowserUk (Pope)
on Jun 16, 2002 at 19:22 UTC ( #174955=perlquestion: print w/ replies, xml ) Need Help??
BrowserUk has asked for the wisdom of the Perl Monks concerning the following question:

I currently am using the line

my @dirs = grep { -d "$base/$_" and $_ ne "." and $_ ne ".." } readdir +(DIR);
to build a list of dirs from the return of readdir. What I now wish to do is build a second list of just the files from the same readdir list. In keeping with Swartzian principle, what I want to write is something like:

my (@dirs,@files) = grep { -d "$base/$_" and $_ ne "." and $_ ne ".." } { -f "$base/$_" } readdir(DIR);
I realise that I can do:

my @all = readdir(DIR); my @dirs = grep {-d "$base/£_" and $ne "." and $_ ne ".." } @all; my @files = grep {-f "$base/£_" } @all;

But my instinct say (probably wrongly) that this can be done without the need to name @all? Thanks.

Comment on Producing 2 lists from a grep call
Select or Download Code
Replies are listed 'Best First'.
•Re: Producing 2 lists from a grep call
by merlyn (Sage) on Jun 16, 2002 at 19:52 UTC
    Applying the KISS principle:
    my @dirs; my @files; for (readdir DIR) { next if $_ eq "." or $_ eq ".."; if (-d "$base/$_") { push @dirs, $_; } else { push @files, $_; } }
    Don't try to cooerce everything into a grep.

    -- Randal L. Schwartz, Perl hacker

      Run that code on /dev and it will get seriously wrong results.

      A file isn't just anything that is not a directory.

Re: Producing 2 lists from a grep call
by dws (Chancellor) on Jun 16, 2002 at 19:52 UTC
    What you've got works, but if you're concerned about space overhead, you could do something like
    foreach my $file ( readdir(DIR) ) { push @dirs, $file if -d "$base/$file" and $file ne "." and $file ne ".."; push @files, $file if -f "$base/$file"; }
Re: Producing 2 lists from a grep call
by tstock (Curate) on Jun 16, 2002 at 20:03 UTC
    You can use a while loop around the readdir call, and push the files into your @files, @dirs arrays using a if, elsif, else construct. no need for intermediary @all.

    If you don't like this solution for being too simple or verbose, then you could always play with map in void context and the ? : switches.

    tstock
      No need for a void map.
      for (readdir DIR) { next if $_ eq "." or $_ eq ".."; next unless -d "$base/$_" or -f _; push @{ -d _ ? \@dirs : \@files }, $_; }
      Update: Excellent point by particle++. Code updated accordingly.

      Makeshifts last the longest.

        I quite like that one. I came close to that but missed (actually, simply wasn't aware of) the @{\@array) 'trick'.

        However (you knew that was coming right?), as I can do:

        my (@a1,@a2) = ([1,2,3],[3,2,1]);

        Update: the above line doesn't work after all

        I had tried:

        my (@a1,@a2) = ([1,2,3,4,5,6,7],[7,6,5,4,3,2,1]); print Dumper(@a1), Dumper(@a2);

        which gave:

        $VAR1 = [ 1, 2, 3, 4, 5, 6, 7 ]; $VAR2 = [ 7, 6, 5, 4, 3, 2, 1 ];
        which certainly lookedlike it had worked! It was only after seeing this that I went back and added a couple of labels

        print "a1\n", Dumper(@a1), "a2\n", Dumper(@a2);

        That I saw what tstock meant below.

        Thanks tstock, I'll be more careful in future with my quick tests. (I agree about the loop/if being better too.)

        end of update

        Which personally I find a very clear yet concise way of declaring and initialising two arrays. It still seems as if something close to:

        my ( @dirs, @files)= ( @{-d}, @{-f} ) for (readdir DIR);

        could be possible if I could only get the syntactic sugar right!

        This is only an exercise in my trying to understand arrays and list contexts etc. ie. Its essentially a purely academic excercise for late (for me) on a Sunday evening, and so not worthy of anyone's time unless they are also in play mode.

        this code does not work -- !-d  !=  -f!

        to implement this code properly, you must test for both -d, and -f, and ignore the rest. add next unless -d $_ or -f _; before the push statement and you'll have what you're looking for.

        for (readdir DIR) { next if $_ eq '.' or $_ eq '..'; next unless -d $base . '/' . $_ or -f _; push @{ -d $base . '/' . $_ ? \@dirs : \@files }, $_; }
        Update: changes as per Screamer

        Update 2: for more info on filetests, see -X under perlfunc

        ~Particle *accelerates*

Re: Producing 2 lists from a grep call
by marvell (Pilgrim) on Jun 17, 2002 at 12:29 UTC
    How about something that is like the following pseudo code:
    for $item @list { next if definite failure condition action if most likely condition action if next most likely condition etc. default action }
    Giving you:
    for (readdir DIR) { next if $_ eq '.' || $_ eq '..'; push (@files,$_) && next if -f $base/$_; push (@dirs,$_) && next if -d $base/$_; # default to no operation }
    I imagine this will look a lot more elegant with the new perl 6 switch statement.

    --
    Steve Marvell

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://174955]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (16)
As of 2015-07-28 19:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (258 votes), past polls