Reputation: 101
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.
Upvotes: 2
Views: 1377
Reputation: 42421
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
Upvotes: 2
Reputation: 66988
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.
Upvotes: 6