我有一组数据文件如下所示。我想通过参考2组数字范围(scoreA和scoreB)得到插值最终值(final,P)。假设“Eric”,他的得分A为35(值范围为30.00 - 40.00),得分B为48(范围45.00 - 50.00之间的值)。他将得到2组最终值范围,即(22.88,40.90)& (26.99,38.99)。我想在数据文件中获得“Eric”和“George”的最终值。 “乔治”的得分A = 38,得分B = 26。
在公式计算之后,我想得到他的得分A = 35&的完全最终值。 scoreB = 45。假设公式为P = X + Y(P是最终值),到目前为止我一直在尝试如下所示的代码。但是它无法获得正确的线条。
如何通过参考给定的数据获得确切的最终值范围?
数据文件
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
答案 0 :(得分:1)
在我的评论中,我说解析相当很容易。这是如何做到的。由于问题缺乏对文件格式的正确规范,我将假设以下内容:
该文件由属性组成,其中包含值:
document ::= property*
property ::= word "(" value ("," value)* ")"
值是双引号字符串,包含以逗号分隔的数字或单个单词:
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;
}
现在,我们将以下数据结构作为Parser::parse
的输出:
(
["Student_name", "Eric"],
["scoreA", [10, 20, 30, 40]],
["scoreB", [15, 30, 45, 50, 55]],
[
"final",
[12.23, 19, 37.88, 45.98, 60],
[7, 20.11, 24.56, 45.66, 57.88],
[5, 15.78, 22.88, 40.9, 57.99],
[10, 16.87, 26.99, 38.99, 40.66],
],
["Student_name", "Liy"],
["scoreA", [5, 10, 20, 60]],
["scoreB", [25, 30, 40, 55, 60]],
[
"final",
[2.23, 15, 37.88, 45.98, 70],
[10, 28.11, 34.56, 45.66, 57.88],
[8, 19.78, 32.88, 40.9, 57.66],
[10, 27.87, 39.99, 59.99, 78.66],
],
...,
)
下一步,我们希望将其转换为嵌套哈希,以便我们有结构
{
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 ));
现在我们可以继续讨论问题的第2部分:计算你的分数。也就是说,一旦你弄清楚你需要做什么。
让 f 成为返回某个坐标值的函数。如果我们在这些坐标处有一个值,我们可以返回。否则,我们使用下一个已知值执行双线性插值。
双线性插值的公式 [1] 是:
f(x, y) = 1/( (x_2 - x_1) · (y_2 - y_1) ) · (
f(x_1, y_1) · (x_2 - x) · (y_2 - y)
+ f(x_2, y_1) · (x - x_1) · (y_2 - y)
+ f(x_1, y_2) · (x_2 - x) · (y - y_1)
+ f(x_2, y_2) · (x - x_1) · (y - y_1)
)
现在,scoreA
表示第一轴final
表中数据点的位置,scoreA
第二轴上的位置x, y
。我们必须做到以下几点:
sub f {
my ($data, $x, $y) = @_;
# do bounds check:
my ($x_min, $x_max, $y_min, $y_max) = (@{$data->{scoreA}}[0, -1], @{$data->{scoreB}}[0, -1]);
die "indices ($x, $y) out of range ([$x_min, $x_max], [$y_min, $y_max])"
unless $x_min <= $x && $x <= $x_max
&& $y_min <= $y && $y <= $y_max;
在边界内
x_1, x_2, y_1, y_2
要获取拳击索引x_i1, x_i2, y_i1, y_i2
,我们需要迭代所有可能的分数。我们还会记住底层数组的物理索引 my ($x_i1, $x_i2, $y_i1, $y_i2);
for ([$data->{scoreA}, \$x_i1, \$x_i2], [$data->{scoreB}, \$y_i1, \$y_i2]) {
my ($scores, $a_i1, $a_i2) = @$_;
for my $i (0 .. $#$scores) {
if ($scores->[$i] <= $x) {
($$a_i1, $$a_i2) = $i == $#$scores ? ($i, $i+1) : ($i-1, $i);
last;
}
}
}
my ($x_1, $x_2) = @{$data->{scoreA}}[$x_i1, $x_i2];
my ($y_1, $y_2) = @{$data->{scoreB}}[$y_i1, $y_i2];
。
f(x_1, y_2)
现在,可以执行根据上述公式的插值,但是可以通过物理索引将已知索引处的每次访问更改为访问,因此$final->[$x_i1][$y_i2]
将成为
sub f
sub f { ... }
f
声明一个名为bilinear_interpolation
的子,虽然这可能是个坏名字。 my ($data, $x, $y) = @_
可能是一个更好的名字。
$data
表示我们的sub有三个参数:
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
在min / max值定义的范围内,否则抛出和出错。
$x
如果$y
或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
例如,会抛出类似
的错误$x
我们可以看到$y
的值太小,scoreA
太大了。
下一个问题是找到相邻的值。假设[1, 2, 3, 4, 5]
持有$x
,3.7
为3
,我们希望选择值4
和2
。但是因为稍后我们可以提取一些漂亮的技巧,我们宁愿记住相邻值的位置,而不是值本身。因此,这将在上面的示例中给出3
和$x
(请记住箭头从零开始)。
我们可以通过循环遍历数组的所有索引来实现。当我们找到一个≤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
}
}
)。
表示为代码,即
$y
在我的原始代码中,我选择了一个更复杂的解决方案,以避免复制粘贴相同的代码以找到my ($x_1, $x_2) = @{$data->{scoreA}}[$x_i1, $x_i2];
的邻居。
因为我们还需要这些值,所以我们通过带有索引的切片获得它们:
$x1, $x_2, $y_1, $y_2
现在我们有了所有周围值*
,它们定义了我们要在其中执行双线性插值的矩形。数学公式很容易转换为Perl:只需选择正确的运算符(·
,而不是$x
进行乘法运算),变量需要前面有美元符号。
我使用的公式是递归: f 的定义指的是它自己。这意味着无限循环,除非我们做一些思考并打破递归。 f 表示某个位置的值。在大多数情况下,这意味着插值。但是,如果$y
和scoreA
分别等于scoreB
和final
中的值,我们就不需要双线性插值,并且可以返回{{1直接进入。
这可以通过检查$x
和$y
是否是其数组的成员并进行早期返回来完成。或者我们可以使用$x_1, ..., $y_2
都是数组成员的事实。我们只需要进行数组访问,而不是使用我们知道不需要插值的值进行递归。这就是我们保存索引$x_i1, ..., $y_i2
的原因。因此,只要原始公式显示为f(x_1, y_1)
或类似,我们就会写出等效的$data->{final}[$x_i1][$y_i2]
。