如何打印具有特定参考编号的范围值?

时间:2013-05-30 14:09:17

标签: perl match lines

我有一组数据文件如下所示。我想通过参考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  

1 个答案:

答案 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。我们必须做到以下几点:

  1. 断言请求的值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; 在边界内
  2. 获取下一个较小的和下一个较大的职位
  3. 执行插值
  4. 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有三个参数:

      1. scoreA,一个包含字段scoreBfinal$x的哈希引用,它们是数组引用。
      2. scoreA$y - 轴的位置,需要插值。
      3. 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

      如果$ydie "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]持有$x3.73,我们希望选择值42。但是因为稍后我们可以提取一些漂亮的技巧,我们宁愿记住相邻值的位置,而不是值本身。因此,这将在上面的示例中给出3$x(请记住箭头从零开始)。

      我们可以通过循环遍历数组的所有索引来实现。当我们找到一个≤3的值时,我们会记住索引。例如。 $x是第一个≤2的值,因此我们记住索引2 + 1 = 3。对于下一个更高的值,我们必须有点小心:显然,我们可以采用下一个索引,所以$x。但现在假设5$x。这会通过边界检查。第一个值<5的值为4,因此我们可以记住位置5。但是,位置3没有条目,因此我们可以使用当前索引本身。因为这会导致稍后除以零,我们最好记住位置44(值5my ($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 表示某个位置的值。在大多数情况下,这意味着插值。但是,如果$yscoreA分别等于scoreBfinal中的值,我们就不需要双线性插值,并且可以返回{{1直接进入。

      这可以通过检查$x$y是否是其数组的成员并进行早期返回来完成。或者我们可以使用$x_1, ..., $y_2都是数组成员的事实。我们只需要进行数组访问,而不是使用我们知道不需要插值的值进行递归。这就是我们保存索引$x_i1, ..., $y_i2的原因。因此,只要原始公式显示为f(x_1, y_1)或类似,我们就会写出等效的$data->{final}[$x_i1][$y_i2]