Overlooping script: how to solve?

Viewed 94

I wrote a simple code to match and select/discard elements across two files. This code works on the $file_in which contains pairs of Id:

Id1 Id2
Id1 Id3
Id1 Id4
Id3 Id4
Id3 Id5
Id3 Id6
Id4 Id5
Id4 Id6

and on the $file_list which contains a list of Id:

Id1
Id2
Id4
Id6
Id7
Id8

What I want is to obtain an output in which are reported only the pairs from $file_in which both Id are included in the $file_list. Thus, my desired output looks like:

Id1 Id2
Id1 Id4
Id4 Id6

The code I'm using overloops across $file_in and gives me this output:

 Id1 Id2
 Id1 Id2
 Id1 Id4
 Id1 Id2
 Id1 Id4
 Id4 Id6
 Id1 Id2
 Id1 Id4
 Id4 Id6
 Id1 Id2
 Id1 Id4
 Id4 Id6

The code follows:

use List::MoreUtils qw(any);

$file_in = "gold_standards.txt";
$file_list = "list.txt";

open (HAN, "< $file_in") || die "Impossible open the in-file...";

my @r = <HAN>;
close (HAN);

open (PEW, "< $file_list") || die "Impossible open list_file...";
@l = <PEW>;
close (PEW);

for ($p=0; $p<=$#l; $p++){
chomp ($l[$p]);

for ($i=0; $i<=$#r; $i++){
    chomp ($r[$i]);     
    @v = split (/\t/, $r[$i],2);
        $id_protein1 = $v[0];
        $id_protein2 = $v[1];


    if ((any {$id_protein1 eq $_} @l) && (any {$id_protein2 eq $_} @l)) {
        print "$r[$i]\n";    
    }
}
}

I'm sure it is a basic issue, but in this moment I cannot fix it. Any help is appreciated.

5 Answers

You can prevent multiple copies by switching the loop nesting and breaking out of the inner loop as soon as you have a success:

#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw(any);

$file_in = "gold_standards.txt";
$file_list = "list.txt";


open (HAN, "< $file_in") or die "Impossible open the in-file...";
my @r = <HAN>;
close (HAN);

open (PEW, "< $file_list") or die "Impossible open list_file...";
my @l = <PEW>;
close (PEW);

chomp (@l, @r);

for my $rv (@r) {
    my ($id_protein1, $id_protein2) = split(/\t/, $rv, 2);

    for my $lv (@l) {
        if ((any {$id_protein1 eq $_} @l) && (any {$id_protein2 eq $_} @l)) {
            print "$rv\n";    
            last;
        }
    }
}

Per @steveo314's answer you can also build a hash and lookup in it:

# ...

my %lh = map { $_ => 1} @l;

for my $rv (@r) {
    my ($id_protein1, $id_protein2) = split(/\t/, $rv, 2);
    print "$rv\n" if (exists $lh{$id_protein1} && exists $lh{$id_protein2});
}

You could also loop through the first file and put whatever field you want into a hash as the hash key. Then step through the second file and if the field is in the hash you print the matching key to the output file. You won't have to have both files open at the same time nor step through each at the same time.

***Rework off my hash idea and what @jhnc came up with

use warnings;
use strict;

open (PEW,"<","list.txt") or die "Cannot open list_file...";
my @l = <PEW>;
close (PEW);
chomp @l;

my %lh = map { $_ => 1} @l;

open (HAN,"<","gold_standards.txt" ) or die "Cannot open the in-file...";
while (<HAN>) {
   chomp;
   my ($id_protein1, $id_protein2) = split(/ /, $_);
   print "$_\n" if (exists $lh{$id_protein1} and exists $lh{$id_protein2});
}
close (HAN);

Flip the order of your 2 for loops, then exit the inner loop using last as soon as a match is found. This is the reason you get duplicates in your output.

use warnings;
use strict;
use List::Util qw(any);

my $file_in   = "gold_standards.txt";
my $file_list = "list.txt";

open( HAN, "< $file_in" ) || die "Impossible open the in-file...";
my @r = <HAN>;
close(HAN);
chomp @r;

open( PEW, "< $file_list" ) || die "Impossible open list_file...";
my @l = <PEW>;
close(PEW);
chomp @l;


for ( my $i = 0 ; $i <= $#r ; $i++ ) {
    my @v           = split( /\s+/, $r[$i], 2 );
    my $id_protein1 = $v[0];
    my $id_protein2 = $v[1];
    for ( my $p = 0 ; $p <= $#l ; $p++ ) {
        if ( ( any { $id_protein1 eq $_ } @l ) && ( any { $id_protein2 eq $_ } @l ) ) {
            print "$r[$i]\n";
            last;
        }
    }
}

I changed \t to \s+ since the tabs show up as spaces when I copied your input file.

Here is another way to store the content key in hash and check the existence in the hash for the content.

Refer the code below:

#!/usr/bin/perl

use strict; use warnings;

my %list_hash;

my $file_in   = "gold_standards.txt";
my $file_list = "list.txt";

open (my $HAN, "<", $file_in) || die "Impossible open the in-file...";

my @r = <$HAN>;
close ($HAN);

open (my $PEW, "<", $file_list) || die "Impossible open list_file...";
my @l = <$PEW>;
close ($PEW);

foreach my $l_element (@l){
    chomp $l_element;
    $list_hash{$l_element} = 1;
}

foreach my $r_element( @r ){
    chomp $r_element;
    
    my @content = split(' ', $r_element);
        
    if(exists $list_hash{$content[0]} && exists $list_hash{$content[1]}){
        print "$r_element\n";
    }
}

A one-liner, for variety:

perl -lane 'if (!$#ARGV) { $h{$F[0]}++; next }
            if ($h{$F[0]} && $h{$F[1]}) { print }' file_list file_in
Related