Дублирование реализации в алгоритме анализа генетической информации


Исходный код

@lib_001=('A','G','C','T','A');
@lib_002=('C','T','A','A','G');		 
@lib_003=('T','G','C','A','T');

$string=<FILE>;
@sequence=split("",$string);
@code=@sequence[-7..-3];
		
if((join('',@lib_001) eq join('',@code)) or
    (join('',@lib_001[0..3]) eq join('',@code[0..3])) or
    (join('',@lib_001[1..4]) eq join('',@code[1..4])) or
    (join('',@lib_001[0,1,2,4]) eq join('',@code[0,1,2,4])) or
    (join('',@lib_001[0,2,3,4]) eq join('',@code[0,2,3,4])) or
    (join('',@lib_001[0,1,3,4]) eq join('',@code[0,1,3,4]))) 
	{$tag=join('',@code);
	 $closest_tag=join('',@lib_001);}
			
elsif((join('',@lib_002) eq join('',@code)) or
     (join('',@lib_002[0..3]) eq join('',@code[0..3])) or
     (join('',@lib_002[1..4]) eq join('',@code[1..4])) or
     (join('',@lib_002[0,1,2,4]) eq join('',@code[0,1,2,4])) or
     (join('',@lib_002[0,2,3,4]) eq join('',@code[0,2,3,4])) or
     (join('',@lib_002[0,1,3,4]) eq join('',@code[0,1,3,4]))) 
	{$tag=join('',@code);
	 $closest_tag=join('',@lib_002);}
		
elsif((join('',@lib_003) eq join('',@code)) or
     (join('',@lib_003[0..3]) eq join('',@code[0..3])) or
     (join('',@lib_003[1..4]) eq join('',@code[1..4])) or
     (join('',@lib_003[0,1,2,4]) eq join('',@code[0,1,2,4])) or
     (join('',@lib_003[0,2,3,4]) eq join('',@code[0,2,3,4])) or
     (join('',@lib_003[0,1,3,4]) eq join('',@code[0,1,3,4]))) 
	{$tag=join('',@code);
	 $closest_tag=join('',@lib_003);}

else
	{$tag=join('',@code);
	  $closest_tag='NONE';}

Как можно улучшить исходный код

Нетрудно видеть, что в исходном коде трижды повторяется реализация проверки некоего условия. Спрячу эту проверку в отдельную функцию:

sub isTagLike (\@\@) {
     my ( @lib, @code ) = @_;
     return (join('',@lib) eq join('',@code)) or
               (join('',@lib[0..3]) eq join('',@code[0..3])) or
               (join('',@lib[1..4]) eq join('',@code[1..4])) or
               (join('',@lib[0,1,2,4]) eq join('',@code[0,1,2,4])) or
               (join('',@lib[0,2,3,4]) eq join('',@code[0,2,3,4])) or
               (join('',@lib[0,1,3,4]) eq join('',@code[0,1,3,4]));
}

if (isTagLike( @lib_001, @code )) {
     $tag = join('', @code);
     $closest_tag = join('', @lib_001);
}
elsif ...

После анализа собственно алгоритма сравнения становится видно, что два параметра считаются совпадающими, если они совпадают в точности, либо без учёта одного из пяти составляющих элементов.

Это позволяет переписать алгоритм проверки в виде цикла и ещё одной функции.

Легко видеть, что дублируется реализация сравнения и запоминания последовательности — её тоже можно легко переписать в виде цикла.

Также сильно хочется избавиться от расклеивания и склеивания строк.

sub isTagLikeWithout {
     my ( $tag, $code, $position ) = @_;
     substr( $tag, $position, 1, '*' );
     substr( $code, $position, 1, '*' );
     return $tag eq $code;
}

sub isTagLike {
     my ( $tag, $code ) = @_;
     if ( $tag eq $code ) return true;
     foreachmy $i (0 .. 4) {
          if ( isTagLikeWithout( $tag, $code, $i ) ) return true;
     }
     return false;
}

@lib = ( 'AGCTA', 'CTAAG', 'TGCAT' );

$string=<FILE>;
$code=substr($string, -7, -3);

foreachmy $lib (@lib) {
     if ( isTagLike( $lib, $code ) ) {
          $tag = $code;
          $closest_tag = $lib;
          last;
     }
}

После рефакторинга знание о реализации сравнения генетической информации переехало в аж две отдельные функции (так как само по себе нетривиально) — что тоже очень хорошо. Остались в одной куче знания о способе получения входной последовательности (из файла), сопоставляемых образцах (@lib) и способе хранения результата сравнения ($tag и $closest_tag), но это пока не так критично.

Теория