我有一段代码,它从两个大的已排序整数数组之一中提取一个切片,表示程序工作流程中的停止点。我将在此处包含两者之一。
基本思想是,我试图从这个大数组中切出一个工作范围,作为该程序工作的起点。 $min
从任务对象中拉取,表示任务的当前进度。 $limit
是可选的用户覆盖,默认为 -1
(被忽略)。
目前,我正在使用 firstidx
函数来自 List::MoreUtils
CPAN 模块来检索开始和结束的索引,然后我使用它们来切片 @steps
按照通常的方式排列。有没有更快和/或更惯用的方法来做到这一点?特别是,有没有直接使用 $min
来做到这一点的好方法?和$limit
值(对于 $limit == -1
情况有另一个代码路径)?
代码如下:
my @steps = (
0, 1, 5, 10,
20, 30, 40, 50, 60, 70, 80, 90, 100,
200, 300, 400, 500, 600, 700, 800, 900, 1000,
1200, 1400, 1600, 1800, 2000,
2500, 3000, 3500, 4000, 4500, 5000,
6000, 7000, 8000, 9000, 10000,
11000, 12000, 13000, 14000, 15000, 17500, 20000,
25000, 30000, 35000, 40000, 45000, 50000,
60000, 70000, 80000, 90000, 100000
);
my $min_index = firstidx { $_ > $min } @steps;
my $max_index;
if ($limit == -1) {
$max_index = @steps - 1;
} else {
$max_index = firstidx { $_ >= $limit } @steps;
}
my @steps_todo = @steps[ $min_index .. $max_index ];
最佳答案
您可以使用简单的 foreach 循环来完成此操作。流程控制关键字控制何时开始或停止使用 @steps
数组。
我将无限制情况更改为定义的检查,因为这样更有效。
这不是最有效的算法,但它是解决该问题的一种简单的惯用方法。
use diagnostics; # Very verbose warnings
($min, $max, @out) = (1000, 9999);
@steps = (
0, 1, 5, 10,
20, 30, 40, 50, 60, 70, 80, 90, 100,
200, 300, 400, 500, 600, 700, 800, 900, 1000,
1200, 1400, 1600, 1800, 2000,
2500, 3000, 3500, 4000, 4500, 5000,
6000, 7000, 8000, 9000, 10000,
11000, 12000, 13000, 14000, 15000, 17500, 20000,
25000, 30000, 35000, 40000, 45000, 50000,
60000, 70000, 80000, 90000, 100000
);
foreach (@steps) {
next if $_ < $min;
last if defined $max and $_ > $max;
push @out, $_;
}
print "@out";
## output
## 1000 1200 1400 1600 1800 2000 2500 3000 3500 4000 4500 5000 6000 7000 8000 9000
这也是触发器运算符 ..
的一个可能用例。它看起来像列表范围运算符,但在标量上下文中它是触发器。可以将其视为用于任意表达式的 cmp
三态运算符。它返回一个定义从“this”到“that”范围的值。称之为挡板。看看这个样本。请参阅perlop Range Operator部分了解更多信息。操作数可以是任何返回 bool 值的值。
use Data::Dump qw/pp/;
($min, $max) = (1000, 9999);
foreach (@steps) {
my $x = ($_ >= $min .. $_ < $max);
printf "%-9s %s\n", $_, pp $x;
}
## Output
0 ""
1 ""
5 ""
10 ""
20 ""
30 ""
40 ""
50 ""
60 ""
70 ""
80 ""
90 ""
100 ""
200 ""
300 ""
400 ""
500 ""
600 ""
700 ""
800 ""
900 ""
1000 "1E0"
1200 "1E0"
1400 "1E0"
1600 "1E0"
1800 "1E0"
2000 "1E0"
2500 "1E0"
3000 "1E0"
3500 "1E0"
4000 "1E0"
4500 "1E0"
5000 "1E0"
6000 "1E0"
7000 "1E0"
8000 "1E0"
9000 "1E0"
10000 1
11000 2
12000 3
13000 4
14000 5
15000 6
17500 7
20000 8
25000 9
30000 10
35000 11
40000 12
45000 13
50000 14
60000 15
70000 16
80000 17
90000 18
100000 19
请注意那些返回“1E0”的值,因为这些值会触发触发器中的复位。在真正的循环中,过渡点是它将终止的地方。您可以使用参数来选择哪组值将获得“”、“1E0”或序列号,并采取相应的操作。
此版本将序列号提供给想要的值,然后开始“E0”重置值。
($_ >= $min .. $_ > $max);
...
800 ""
900 ""
1000 1
1200 2
...
8000 15
9000 16
10000 "17E0"
11000 "1E0"
12000 "1E0"
...
以下是使用触发器解决该问题的方法。这种方式的优点是一旦达到下限条件就不再计算。主要缺点是它非常神秘,非 Perl 程序员可能无法理解。但这是尽可能紧凑和高效的。
foreach (@steps) {
($_ >= $min .. ($_ > $max and last)) or next;
push @out, $_;
}
print "@out\n";
## 1000 1200 1400 1600 1800 2000 2500 3000 3500 4000 4500 5000 6000 7000 8000 9000
虽然左挡板为假,但整个表达式为假,我们进入下一项。当左挡板变为真时,整个表达式现在为真,我们继续并开始检查右挡板。虽然右挡板为 false(整个表达式仍然为 true),但我们继续将项目保存在 @out
中。当右挡板变为真时,我们终止循环。 (这也是它会重置回 false 的地方。)
再进行一次编辑。 :)
如果没有最大值,这里有一个好方法。
for (my $i = 0; $i <= $#steps; $i++) {
next unless $steps[$i] >= $min;
@out = @steps[$i .. $#steps];
# Or, if you don't need the original array anymore you can consume it
# and be a bit more efficient and save memory
# @out = splice @steps, $i;
last;
}
好问题!
关于arrays - 按值而不是索引对 Perl 数组进行切片的惯用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69969430/