user1981275
user1981275

Reputation: 13372

extract array elements between matches

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

Answers (3)

John C
John C

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

Miller
Miller

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

Borodin
Borodin

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

Related Questions