我想使用 start、end 和 step 对字符串进行切片。让我们假设开始坐标是 1,结束坐标是 22,步长是 3。切片字符串应该只选择坐标 1、4、7、10、13、16、19、22 中的元素。我写了一个函数 get_subseq这样做。在 Perl 中是否有更短的方法来做到这一点?
sub get_subseq {
my ( $seq, $start, $end, $step ) = @_;
my $index = $start;
while ( $index <= $end ) {
print substr $seq, $index, 1;
$index += $step;
}
}
my $sequence = 'AGGGTAGAGTGAGAAGCACCAGCAGGCAGTAACAGC';
# The result should be GTAGACCC
get_subseq( $sequence, 1, 22, 3 );
Run Code Online (Sandbox Code Playgroud)
一种方法:生成索引列表,然后用于map获取相应的字符。
合并成一个声明
use warnings;
use strict;
use feature 'say';
my $seq = q(AGGGTAGAGTGAGAAGCACCAGCAGGCAGTAACAGC);
my ($beg, $end, $step) = (1, 22, 3);
my @subseq =
map { substr $seq, $_, 1 }
grep { ($_-$beg) % $step == 0 }
$beg..$end;
say "@subseq";
Run Code Online (Sandbox Code Playgroud)
这可以在$beg..$end范围内折叠为一次迭代
my @subseq =
map { ($_-$beg) % $step == 0 ? substr($seq, $_, 1) : () }
$beg..$end;
Run Code Online (Sandbox Code Playgroud)
如果结果需要是一个字符串join列表''(空字符串)。
当然还有一些库可以产生一个跨度范围。该列表::根具有这样的range功能,同时它还具有有趣的算法的整个范围。
use List::Gen qw(range);
my @ss = map { substr $seq, $_, 1 } @{ range $beg, $end, $step };
say "@ss";
Run Code Online (Sandbox Code Playgroud)
它range返回一个真正的生成器,它具有有趣的特性。取消引用它会生成值列表。请参阅文档。
虽然这些在单个语句中返回结果,因此“更短”,但我喜欢你自己的问题,它非常清晰,在许多情况下可能更有效。
可以通过(很少)使用 C 样式for循环来进一步简化
for (my $i = $beg; $i <= $end; $i += $step) { print substr $seq, $i, 1 }
Run Code Online (Sandbox Code Playgroud)
另一种方法是将您的字符串分解为其字符列表,然后从该列表中提取所需位置的元素
my @subseq = (split //, $seq)[ @indices ];
Run Code Online (Sandbox Code Playgroud)
您可以在其中使用任何方法来获取@indices(不需要是数组,但可以是在那里生成的列表,例如通过上面使用的任何方法)。这两种方法中哪一种更有效完全取决于细节——序列的长度、要采样的索引跨度的长度、它们的关系、步长。
没有人喜欢使用正则表达式吗?
join'',substr($seq,$start,$end-$start+1)=~/(?=(.)).{0,$step}/gs
Run Code Online (Sandbox Code Playgroud)
这演示了对这种事情使用正则表达式匹配,其方式有时比循环或拆分和切片更有效。一种更有趣但效率不高的方法是避开 substr:
join '',$seq=~/(?<=.{$start})(?<!..{$end})(?=(.)).{0,$step}/gs
Run Code Online (Sandbox Code Playgroud)