tags:

views:

123

answers:

2
+1  Q: 

Perl Map Function

I'm new to the map and grep functions and I'm trying to make an existing script more concise.

I can "grep" the @tracknames successfully but I'm having a problem with "map". I want @trackartist to return true if two consecutive "--" are found in a line and take the value of $1, otherwise false, but it returns the whole line if the upper condition is not met. What am I doing wrong?

my @tracknames = grep /^\d\d\..*?(\.(?:flac|wv))$/, <*.*>;
my @trackartist = map { s/^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/$1/; $_; } <*.*>;

Sample of files
01. some track artist 1 -- some track name 1.(flac or wv)
02. some track artist 2 -- some track name 2.(flac or wv)
03. some track artist 3 -- some track name 3.(flac or wv)
etc.
+6  A: 

Remember that grep is for filtering a list and map is for transforming a list. Right now, your map statement returns $_ for every item in the list. If $_ matches the pattern in your substitution, it will be modified and replaced with the first match. Otherwise, it's not modified and the original $_ is returned.

It sounds like you want to filter out items that don't match the pattern. One way would be to combine a map and a grep:

my @trackartist = map { s/^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/$1/; $_; }
                  grep { /^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/ } <*.*>;

Of course, this means you're doing the same pattern match twice. Another approach is to do a transform with map, but transform anything that doesn't match the pattern into an empty list.

my @trackartist = map { /^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/ ? $1 : ( ) } <*.*>

This uses the ternary conditional operator (?:) to check if the regex matches (returning a true value). If it does, $1 is returned from the map block, if not, an empty list ( ) is returned, which adds nothing to the list resulting from the map.

As a side note, you might want to look into using the glob function rather than <>, which has some disadvantages.

friedo
Thanks for your help. From the explanation I concluded that "map" might not be the most suitable solution in my case and I might try a different approach. Is this the right code if pattern match doesn't succeed - return the value of $artist. I'll definitely look into the glob function. my @trackartist = map { /^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/; $1 ? $1 : ($artist) } <*.*>;
thebourneid
This idiom is better expressed as `/.../ ? $1 : ()`.
Greg Bacon
Good point, gbacon. Updated.
friedo
+2  A: 

I like map and grep as much as the next guy, but your task seems more suited to a divide-and-conquer parsing approach. I say this because your comments suggest that your interest in map is leading you down a road where you'll end up with a data model consisting of parallel arrays -- @tracks, @artists, etc. -- which is often difficult to maintain in the long run. Here's a sketch of what I mean:

my @tracks;

while (my $file_name = <DATA>){ # You'll use glob() or <*.*>
    # Filter out unwanted files.
    my ($num, $artist_title, $ext) = $file_name =~ /
        ^ (\d\d) \. \s*
        (.*)
        \. (flac|wv) $
    /x;
    next unless $ext;

    # Try to parse the artist and title. Adjust as needed.
    my ($artist, $title) = split /\s+--\s+/, $artist_title, 2;
    ($artist, $title) = ('UNKNOWN', $artist) unless $title;

    # Store all info as a hash ref. No need for parallel arrays.
    push @tracks, {
        file_name  => $file_name,
        ext        => $ext,
        artist     => $artist,
        title      => $title,
    };
}

__DATA__
01. Perl Jam -- Open or die.wv
02. Perl Jam -- Map to nowhere.flac
03. Perl Jam -- What the #$@!?.wv
04. Perl Jam -- Regex blues.wv
05. Perl Jam -- Use my package, baby.wv
06. Perl Jam -- No warnings.wv
07. Perl Jam -- Laziness ISA virtue.wv
08. Guido and the Pythons -- Home on the xrange.flac
09. Guido and the Pythons -- You gotta keep em generated.flac
10. StackOverflow medley.wv
foo.txt
FM