lamp has asked for the wisdom of the Perl Monks concerning the following question:

While optimizing an application I found the following code snippet, which is deleting comma separated hash value.
Here is what the code is doing:

  • get the comma separated value of '%new_reports' hash based on each '@key_to_search' array elements.
  • search for '@element_to_delete' array elements in comma separated value of hash and delete the element if found.
    Also delete '%new_reports' hash key, If no value exist for that particular key.

    Here is the Dumper output before deleting.
    $VAR1 = { '10' => 'Test1,Test2,Test3,Test4,Test5,Test6', '20' => 'Test4' };
    Dumper output after deleting
    $VAR1 = { '10' => 'Test1,Test3,Test5,Test6' };

    Code taken from the original application.
    #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my %new_reports = ( 10 => 'Test1,Test2,Test3,Test4,Test5,Test6', 20 => 'Test4' ); print Dumper(\%new_reports); my @key_to_search = qw(10 20); my @element_to_delete = qw(Test2 Test4); foreach (@key_to_search) { if($new_reports{$_}) { my @sel_reports = split(/,/,$new_reports{$_}); map{ my $element_to_remove = $_; @sel_reports = grep { $_ ne $element_to_remove } @ +sel_reports; }@element_to_delete; if(scalar(@sel_reports) > 0) { $new_reports{$_} = join(",",@sel_reports); } else { delete $new_reports{$_}; } } } print Dumper(\%new_reports);

    Is there any better way to acheive the same?

    --lamp
  • Replies are listed 'Best First'.
    Re: better way delete comma separated hash values?
    by ikegami (Patriarch) on Sep 29, 2008 at 12:05 UTC

      better way delete comma separated hash values?

      The answer is in the question. Don't use comma separated hash values.

      #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my %new_reports = ( 10 => [qw( Test1 Test2 Test3 Test4 Test5 Test6 )], 20 => [qw( Test4 )], ); my @key_to_search = qw( 10 20 ); my @ele_to_delete = qw( Test2 Test4 ); print Dumper(\%new_reports); my %ele_to_delete = map {$_=>1} @ele_to_delete; for my $key ( @key_to_search ) { my $list = $new_reports{$key}; @$list = grep !$ele_to_delete{$_}, @$list; delete $new_reports{$key} if !@$list; } print Dumper(\%new_reports);

      Or if you had a HoH,

      #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my %new_reports = ( 10 => { map {$_=>1} qw( Test1 Test2 Test3 Test4 Test5 Test6 ) }, 20 => { map {$_=>1} qw( Test4 ) }, ); my @key_to_search = qw( 10 20 ); my @ele_to_delete = qw( Test2 Test4 ); print Dumper(\%new_reports); for my $key ( @key_to_search ) { delete @{ $new_reports{$key} }{ @ele_to_delete }; delete $new_reports{$key} if !%{ $new_reports{$key} }; } print Dumper(\%new_reports);
    Re: better way delete comma separated hash values?
    by betterworld (Curate) on Sep 29, 2008 at 11:57 UTC

      You could use a hash of arrays.

      Your code translates back and forth between lists and comma separated strings. This would be unnessecary if you wouldn't store the data as strings in the first place. Of course, that depends on how the rest of your program uses these values.

    Re: better way delete comma separated hash values?
    by salva (Canon) on Sep 29, 2008 at 11:58 UTC
      using a regular expression to search and remove the elements in place could be faster:
      my $delete = join('|', map quotemeta, sort @element_to_delete); my $re = qr/(?:^|,)(?:$delete)(?=,|$)/; for (@key_to_search) { if (defined $new_reports{$_}) { $new_reports{$_} =~ s/$re//g; delete $new_reports{$_} unless length $new_reports{$_} } }
      updated following ikegami advice
        Your regexp is only bounded on one end, but it probably should be bounded on both. Let's not convert "pineapple,apple,banana" into "pinebanana" when deleting "apple".