Reputation: 13372
I have an array of strings and I would like to extract the elements of the array which are between a string that contains the keyword 'start' until a string containing the character ';' occurs. The elements between these keywords should be split by a single space and written into a hash.
Example: For the array (" kk", "aa1", " 1 asa ", " start", "a 1", "b 2", "c 3", ";", "aaa")
I would like to extract the key/value pairs a 1
, b 2
, c 3
and write them into a hash.
My implementation gives the right result but is rather naive and involves looping and nested if statements:
my @lines = ( "z 10", " start", "a 1", "b 2", "c 3", ";", "aaa" );
my %map;
my $start;
foreach my $line (@lines) {
chomp $line;
if ( $line =~ m/start/ ) {
$start = 1;
}
else {
if ($start) {
if ( $line =~ m/;/ ) {
last;
}
my @arr = split " ", $line;
$map{ $arr[0] } = $arr[1];
}
}
}
foreach my $k ( keys %map ) {
my $val = $map{$k};
print "Key : $k val : $val \n";
}
Is there a more elegant way to do this?
Upvotes: 2
Views: 97
Reputation: 4396
This problem is a good candidate for the flip flop operator...
use strict;
use warnings;
use Data::Dumper;
my @lines = (" kk", "aa1", " 1 asa ", " start", "a 1", "b 2", "c 3", ";", "aaa");
my @sub;
foreach (@lines) {
push(@sub, split) if (/ start/ .. /\;/); # flip flop
}
shift @sub; # Remove the start flag.
pop @sub; # Remove the end flag.
my %hash = @sub; # Convert to hash.
print Dumper(\%hash);
Output:
$VAR1 = {
'c' => '3',
'a' => '1',
'b' => '2'
};
Flip flop is the /start/ .. /end/ part.
As pointed out in the comments this can be further refactored as:
my @sub = map +split, grep { / start/ .. /;/ } @lines;
shift @sub;
pop @sub;
my %hash = @sub;
or also:
my @sub = map { / start/ .. /;/ ? split : () } @lines;
shift @sub;
pop @sub;
my %hash = @sub;
These achieve the same result but I have left my first solution as I think it helps to illustrate what the flip flop operator is doing.
Upvotes: 1
Reputation: 35198
I actually like Borodin's solution the most given it's readability.
However, you can do a test on the range return value to limit your results:
use strict;
use warnings;
my @lines = ( "z 10", " start", "a 1", "b 2", "c 3", ";", "aaa" );
my %hash = map {split} grep {
my $r = /start/ .. /;/;
$r && $r !~ /^1$|E/
} @lines;
use Data::Dump;
dd \%hash;
Outputs:
{ a => 1, b => 2, c => 3 }
Upvotes: 1
Reputation: 126722
This can be done most tidily using a single map
call:
my @subset = grep { /start/../;/ } @lines;
my %map = map split, @subset[1 .. $#subset-1];
for my $k (keys %map) {
my $val = $map{$k};
print "Key : $k val : $val \n";
}
output
Key : a val : 1
Key : c val : 3
Key : b val : 2
Upvotes: 2