Perl 如何打印带有特定参考号的范围值?
我有一组数据文件,如下所示。我想通过参考2组数字范围(scoreA和scoreB)得到插值最终值(final,P)。比如说“Eric”,他的分数是35(范围30.00-40.00),分数是48(范围45.00-50.00)。他将得到两组最终值范围,即(22.88,40.90)和(26.99,38.99)。我想得到数据文件中“Eric”和“George”的最终值。“乔治”的得分A=38,得分B=26 经过公式计算,我想得到当他的分数A=35和分数B=45时的精确最终值。让我们假设公式是P=X+Y(P是最终值),到目前为止,我一直在尝试下面所示的代码。但是,它无法获得正确的行 如何根据给定的数据得到准确的最终值范围 数据文件Perl 如何打印带有特定参考号的范围值?,perl,match,lines,Perl,Match,Lines,我有一组数据文件,如下所示。我想通过参考2组数字范围(scoreA和scoreB)得到插值最终值(final,P)。比如说“Eric”,他的分数是35(范围30.00-40.00),分数是48(范围45.00-50.00)。他将得到两组最终值范围,即(22.88,40.90)和(26.99,38.99)。我想得到数据文件中“Eric”和“George”的最终值。“乔治”的得分A=38,得分B=26 经过公式计算,我想得到当他的分数A=35和分数B=45时的精确最终值。让我们假设公式是P=X+Y(
Student_name ("Eric")
/* This is a junk line */
scoreA ("10.00, 20.00, 30.00, 40.00")
scoreB ("15.00, 30.00, 45.00, 50.00, 55.00")
final (
"12.23,19.00,37.88,45.98,60.00",\
"07.00,20.11,24.56,45.66,57.88",\
"05.00,15.78,22.88,40.90,57.99",\
"10.00,16.87,26.99,38.99,40.66"\)
Student_name ("Liy")
/* This is a junk line */
scoreA ("5.00, 10.00, 20.00, 60.00")
scoreB ("25.00, 30.00, 40.00, 55.00, 60.00")
final (
"02.23,15.00,37.88,45.98,70.00",\
"10.00,28.11,34.56,45.66,57.88",\
"08.00,19.78,32.88,40.90,57.66",\
"10.00,27.87,39.99,59.99,78.66"\)
Student_name ("Frank")
/* This is a junk line */
scoreA ("2.00, 15.00, 25.00, 40.00")
scoreB ("15.00, 24.00, 38.00, 45.00, 80.00")
final (
"02.23,15.00,37.88,45.98,70.00",\
"10.00,28.11,34.56,45.66,57.88",\
"08.00,19.78,32.88,40.90,57.66",\
"10.00,27.87,39.99,59.99,78.66"\)
Student_name ("George")
/* This is a junk line */
scoreA ("10.00, 15.00, 20.00, 40.00")
scoreB ("25.00, 33.00, 46.00, 55.00, 60.00")
final (
"10.23,25.00,37.88,45.98,68.00",\
"09.00,28.11,34.56,45.66,60.88",\
"18.00,19.78,32.88,40.90,79.66",\
"17.00,27.87,40.99,59.99,66.66"\)
data();
sub data() {
my $cnt = 0;
while (my @array = <FILE>) {
foreach $line(@array) {
if ($line =~ /Student_name/) {
$a = $line;
if ($a =~ /Eric/ or $cnt > 0 ) {
$cnt++;
}
if ( $cnt > 1 and $cnt <= 3 ) {
print $a;
}
if ( $cnt > 2 and $cnt <= 4 ) {
print $a;
}
if ( $cnt == 5 ) {
$cnt = 0;
}
}
}
}
}
Eric final=42.66
George final=24.30
编码
Student_name ("Eric")
/* This is a junk line */
scoreA ("10.00, 20.00, 30.00, 40.00")
scoreB ("15.00, 30.00, 45.00, 50.00, 55.00")
final (
"12.23,19.00,37.88,45.98,60.00",\
"07.00,20.11,24.56,45.66,57.88",\
"05.00,15.78,22.88,40.90,57.99",\
"10.00,16.87,26.99,38.99,40.66"\)
Student_name ("Liy")
/* This is a junk line */
scoreA ("5.00, 10.00, 20.00, 60.00")
scoreB ("25.00, 30.00, 40.00, 55.00, 60.00")
final (
"02.23,15.00,37.88,45.98,70.00",\
"10.00,28.11,34.56,45.66,57.88",\
"08.00,19.78,32.88,40.90,57.66",\
"10.00,27.87,39.99,59.99,78.66"\)
Student_name ("Frank")
/* This is a junk line */
scoreA ("2.00, 15.00, 25.00, 40.00")
scoreB ("15.00, 24.00, 38.00, 45.00, 80.00")
final (
"02.23,15.00,37.88,45.98,70.00",\
"10.00,28.11,34.56,45.66,57.88",\
"08.00,19.78,32.88,40.90,57.66",\
"10.00,27.87,39.99,59.99,78.66"\)
Student_name ("George")
/* This is a junk line */
scoreA ("10.00, 15.00, 20.00, 40.00")
scoreB ("25.00, 33.00, 46.00, 55.00, 60.00")
final (
"10.23,25.00,37.88,45.98,68.00",\
"09.00,28.11,34.56,45.66,60.88",\
"18.00,19.78,32.88,40.90,79.66",\
"17.00,27.87,40.99,59.99,66.66"\)
data();
sub data() {
my $cnt = 0;
while (my @array = <FILE>) {
foreach $line(@array) {
if ($line =~ /Student_name/) {
$a = $line;
if ($a =~ /Eric/ or $cnt > 0 ) {
$cnt++;
}
if ( $cnt > 1 and $cnt <= 3 ) {
print $a;
}
if ( $cnt > 2 and $cnt <= 4 ) {
print $a;
}
if ( $cnt == 5 ) {
$cnt = 0;
}
}
}
}
}
Eric final=42.66
George final=24.30
在我的评论中,我说解析相当容易。以下是如何做到这一点。由于该问题缺乏适当的文件格式规范,我将假设如下: 该文件由属性组成,这些属性具有以下值: 值是双引号字符串,包含以逗号分隔的数字或单个单词:
value ::= '"' ( word | number ("," number)* ) '"'
空格、反斜杠和注释是不相关的
这是一个可能的实现;我不会详细解释如何编写一个简单的解析器
package Parser;
use strict; use warnings;
sub parse {
my ($data) = @_;
# perform tokenization
pos($data) = 0;
my $length = length $data;
my @tokens;
while(pos($data) < $length) {
next if $data =~ m{\G\s+}gc
or $data =~ m{\G\\}gc
or $data =~ m{\G/[*].*?[*]/}gc;
if ($data =~ m/\G([",()])/gc) {
push @tokens, [symbol => $1];
} elsif ($data =~ m/\G([0-9]+[.][0-9]+)/gc) {
push @tokens, [number => 0+$1];
} elsif ($data =~ m/\G(\w+)/gc) {
push @tokens, [word => $1];
} else {
die "unreckognized token at:\n", substr $data, pos($data), 10;
}
}
return parse_document(\@tokens);
}
sub token_error {
my ($token, $expected) = @_;
return "Wrong token [@$token] when expecting [@$expected]";
}
sub parse_document {
my ($tokens) = @_;
my @properties;
push @properties, parse_property($tokens) while @$tokens;
return @properties;
}
sub parse_property {
my ($tokens) = @_;
$tokens->[0][0] eq "word"
or die token_error $tokens->[0], ["word"];
my $name = (shift @$tokens)->[1];
$tokens->[0][0] eq "symbol" and $tokens->[0][1] eq '('
or die token_error $tokens->[0], [symbol => '('];
shift @$tokens;
my @vals;
VAL: {
push @vals, parse_value($tokens);
if ($tokens->[0][0] eq 'symbol' and $tokens->[0][1] eq ',') {
shift @$tokens;
redo VAL;
}
}
$tokens->[0][0] eq "symbol" and $tokens->[0][1] eq ')'
or die token_error $tokens->[0], [symbol => ')'];
shift @$tokens;
return [ $name => @vals ];
}
sub parse_value {
my ($tokens) = @_;
$tokens->[0][0] eq "symbol" and $tokens->[0][1] eq '"'
or die token_error $tokens->[0], [symbol => '"'];
shift @$tokens;
my $value;
if ($tokens->[0][0] eq "word") {
$value = (shift @$tokens)->[1];
} else {
my @nums;
NUM: {
$tokens->[0][0] eq 'number'
or die token_error $tokens->[0], ['number'];
push @nums, (shift @$tokens)->[1];
if ($tokens->[0][0] eq 'symbol' and $tokens->[0][1] eq ',') {
shift @$tokens;
redo NUM;
}
}
$value = \@nums;
}
$tokens->[0][0] eq "symbol" and $tokens->[0][1] eq '"'
or die token_error $tokens->[0], [symbol => '"'];
shift @$tokens;
return $value;
}
作为下一步,我们希望将其转换为嵌套哈希,以便获得结构
{
Eric => {
scoreA => [...],
scoreB => [...],
final => [[...], ...],
},
Liy => {...},
...,
}
因此,我们只需通过以下小部件运行它:
sub properties_to_hash {
my %hash;
while(my $name_prop = shift @_) {
$name_prop->[0] eq 'Student_name' or die "Expected Student_name property";
my $name = $name_prop->[1];
while( @_ and $_[0][0] ne 'Student_name') {
my ($prop, @vals) = @{ shift @_ };
if (@vals > 1) {
$hash{$name}{$prop} = \@vals;
} else {
$hash{$name}{$prop} = $vals[0];
}
}
}
return \%hash;
}
所以我们有了主代码
my $data = properties_to_hash(Parser::parse( $file_contents ));
现在我们可以进入问题的第二部分:计算你的分数。也就是说,一旦你明确了你需要做什么
编辑:双线性插值
设f为在某个坐标处返回值的函数。如果我们在这些坐标上有一个值,我们可以返回它。否则,我们将使用下一个已知值执行双线性插值
双线性插值的公式为:
现在,scoreA
表示final
表中数据点在第一轴上的位置,scoreA
表示第二轴上的位置。我们必须做到以下几点:
x,y
在范围内f(x_1,y_2)
将变为
$final->[$x_i1][$y_i2]
子f的详细说明
声明了一个名为sub f{…}
的sub,尽管这可能是个坏名字<代码>双线性插值可能是一个更好的名称f
说明我们的sub接受三个参数:my($data,$x,$y)=@
,一个散列引用,包含字段$data
、scoreA
和scoreB
,这些字段是数组引用final
,沿$x
轴的位置,需要插值scoreA
,沿$y
轴的位置,需要插值scoreB
- 接下来,我们要断言
和$x
的位置在边界内是有效的。$y
中的第一个值是最小值;最大值位于最后一个位置(索引$data->{scoreA}
)。为了同时获得这两个数据,我们使用一个数组切片。切片一次访问多个值并返回一个列表,如-1
。因为我们使用的是使用引用的复杂数据结构,所以必须在@array[1,2]
中取消对数组的引用。这使切片看起来像$data->{scoreA}
现在我们有了@{$data->{scoreA}[0,1]
和$x_min
值,我们抛出并出错,除非请求的值$x_max
在最小/最大值定义的范围内。这是真的当$x
例如,可以抛出如下错误$x_min <= $x && $x <= $x_max
在这里我们可以看到,indices (10, 500) out of range ([20, 30], [25, 57]) at script.pl line 42
的值太小,$x
太大$y
- 下一个问题是找到邻近的值。假设
保持scoreA
,并且[1,2,3,4,5]
是$x
,我们要选择3.7
和3
的值。但是,因为我们可以在稍后使用一些巧妙的技巧,我们宁愿记住相邻值的位置,而不是值本身。因此,在上面的示例中,这将给出4
和2
(请记住,箭头是以零为基础的) 我们可以通过循环数组的所有索引来实现这一点。当我们找到一个≤ <代码>$x,我们记得索引。例如,3
是≤3
,因此我们记住了索引$x
。对于下一个更高的值,我们必须有点谨慎:显然,我们可以只取下一个索引,所以2
。但是现在假设2+1=3
是$x
。这通过了边界检查。第一个值是≤5
将是值$x
,因此我们可以记住位置5
。但是,位置4
没有条目,因此我们可以使用当前索引本身。因为这将导致以后被零除,我们最好记住位置5
和3
(值4
和4
) 表示为代码,即5
my ($x_i1, $x_i2); my @scoreA = @{ $data->{scoreA} }; # shortcut to the scoreA entry for my $i (0 .. $#scores) { # iterate over all indices: `$#arr` is the last idx of @arr if ($scores[$i] <= $x) { # do this if the current value is ≤ $x if ($i != $#scores) { # if this isn't the last index ($x_i1, $x_i2) = ($i, $i+1); } else { # so this is the last index ($x_i1, $x_i2) = ($i-1, $i); } last; # break out of the loop } }
- 现在我们有了所有周围的值
,它们定义了要在其中执行双线性插值的矩形。数学公式很容易翻译成Perl:只需选择正确的运算符($x1、$x_2、$y_1、$y_2
,而不是*
)·
die "indices ($x, $y) out of range ([$x_min, $x_max], [$y_min, $y_max])"
indices (10, 500) out of range ([20, 30], [25, 57]) at script.pl line 42
my ($x_i1, $x_i2); my @scoreA = @{ $data->{scoreA} }; # shortcut to the scoreA entry for my $i (0 .. $#scores) { # iterate over all indices: `$#arr` is the last idx of @arr if ($scores[$i] <= $x) { # do this if the current value is ≤ $x if ($i != $#scores) { # if this isn't the last index ($x_i1, $x_i2) = ($i, $i+1); } else { # so this is the last index ($x_i1, $x_i2) = ($i-1, $i); } last; # break out of the loop } }
my ($x_1, $x_2) = @{$data->{scoreA}}[$x_i1, $x_i2];