h.mon
h.mon

Reputation: 258

Alternative to foreach loop with hashes in perl

I have two files, one with text and another with key / hash values. I want to replace occurrences of the key with the hash values. The following code does this, what I want to know is if there is a better way than the foreach loop I am using.

Thanks all

Edit: I know it is a bit strange using

s/\n//;
s/\r//;

instead of chomp, but this works on files with mixed end of line characters (edited both on windows and linux) and chomp (I think) does not.

File with key / hash values (hash.tsv):

strict  $tr|ct
warnings    w@rn|ng5
here    h3r3

File with text (doc.txt):

Do you like use warnings and strict?
I do not like use warnings and strict.
Do you like them here or there?
I do not like them here or there?
I do not like them anywhere.
I do not like use warnings and strict.
I will not obey your good coding practice edict. 

The perl script:

#!/usr/bin/perl

use strict;
use warnings;
open (fh_hash, "<", "hash.tsv") or die "could not open file $!";
my %hash =();
while (<fh_hash>)
{
    s/\n//;
    s/\r//;
    my @tmp_hash = split(/\t/);
    $hash{ @tmp_hash[0] } = @tmp_hash[1];
}
close (fh_hash);
open (fh_in, "<", "doc.txt") or die "could not open file $!";
open (fh_out, ">", "doc.out") or die "could not open file $!";
while (<fh_in>)
{
    foreach my $key ( keys %hash )
    {
        s/$key/$hash{$key}/g;
    }
    print fh_out;
}
close (fh_in);
close (fh_out);

Upvotes: 4

Views: 1170

Answers (2)

clt60
clt60

Reputation: 63912

You can read a whole file into a variable a replace all occurrences at once for each key-val.

Something like:

use strict;
use warnings;

use YAML;
use File::Slurp;
my $href = YAML::LoadFile("hash.yaml");
my $text = read_file("text.txt");

foreach (keys %$href) {
    $text =~ s/$_/$href->{$_}/g;
}
open (my $fh_out, ">", "doc.out") or die "could not open file $!";
print $fh_out $text;
close $fh_out;

produces:

Do you like use w@rn|ng5 and $tr|ct?
I do not like use w@rn|ng5 and $tr|ct.
Do you like them h3r3 or th3r3?
I do not like them h3r3 or th3r3?
I do not like them anywh3r3.
I do not like use w@rn|ng5 and $tr|ct.
I will not obey your good coding practice edict. 

For shorting a code i used YAML and replaced your input file with:

strict: $tr|ct
warnings: w@rn|ng5
here: h3r3

and used File::Slurp for reading a whole file into a variable. Of course, you can "slurp" the file without File::Slurp, for example with:

my $text;
{
    local($/); #or undef $/;
    open(my $fh, "<", $file ) or die "problem $!\n";
    $text = <$fh>;
    close $fh;
}

Upvotes: 2

ikegami
ikegami

Reputation: 385809

One problem with

for my $key (keys %hash) {
    s/$key/$hash{$key}/g;
}

is it doesn't correctly handle

foo => bar
bar => foo

Instead of swapping, you end up with all "foo" or all "bar", and you can't even control which.

# Do once, not once per line
my $pat = join '|', map quotemeta, keys %hash;

s/($pat)/$hash{$1}/g;

You might also want to handle

foo  => bar
food => baz

by taking the longest rather than possibly ending with "bard".

# Do once, not once per line
my $pat =
   join '|',
    map quotemeta,
     sort { length($b) <=> length($a) }
      keys %hash;

s/($pat)/$hash{$1}/g;

Upvotes: 2

Related Questions