4

我已经工作了几个星期,试图让一个 perl 程序工作。其他人编写了它,从那时起数据源已更改。我花了数周时间逐行搜索并做教程。我被困住了。代码说@{ $Routings{$Code} }which 有一个值列表,但是当它似乎没有返回数据时,它[ $ProcessID, $Setup, $Process ]位于代码的底部。foreach ( @{ $Routings{$Code} } ) {my $ProcessCodeID = @$_[0];}如果有人甚至可以帮助我print $ProcessCodeID,以便我可以跟踪数据,那将非常有帮助。

此外,如果你能解释什么@{$value{$key}}代表那也会有帮助。

多谢了。

%Routings = ();
my $dbh = DBI-> connect('dbi:ODBC:SQL')
    or die "Couldn't open Databaxe: $DBI::errstr;  stopped";

my $query= $dbh->prepare("SELECT Code, Setup, Process, ProcessID FROM ROUTING");

$query->execute() or die "Couldn't execute statement: $DBI::errstr; stopped";

while ( my ($Code, $setup, $process, $processid) = $query->fetchrow_array() ){
    push ( @{ $Routings{$Code} }, [ $ProcessID, $Setup, $Process ] );
}

foreach ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = @$_[0];
    my $SetupMins = @$_[1];
    my $ProcessMins = @$_[2];
}
4

4 回答 4

6

首先,重要的是您use strictuse warnings在程序开始时,并在首次使用它们时声明所有变量。这将导致 Perl 生成一些非常有用的消息,这些消息将揭示许多容易被忽略的简单错误。

例如,您正在分配变量$setup, $process,$processid然后将$Setup,$Process$ProcessID推入一个数组。Perl 标识符区分大小写,因此它们是三个不同的变量,undef此时的值为 。use strict会打印一个编译错误,说明$ProcessID尚未声明等。(如果您有选择,最好使用小写加下划线来表示这些本地标识符。经验丰富的 Perl 程序员会感谢您。)

您应该尝试使用该Data::Dumper模块,该模块将显示像这样的复杂嵌套 Perl 数据结构的内容和结构。一旦你有use Data::Dumper你的程序,你可以写

print Dumper \%Routings

这会将 的内容显示%Routings为匿名哈希。

散列的每个元素$Routings{$Code}的值是与 Code 的值对应的所有 ProcessID、Setup 和 Process 集合的列表(对数组的引用)。(我假设该列Code是非唯一的,否则数据结构比它需要的更复杂。)因此,给定的三个值的第一组$Code是 at$Routings{$Code}[0]并且该ProcessID组的 the 是$Routings{$Code}[0][0]

没有代码可以$Codeforeach循环分配值,并且大概您希望遍历%Routings散列的所有键。

每次foreach循环$_都设置为对 current 的每个三元组值的引用$Code。这意味着@$_是一个三元素数组,但它应该使用$_->[0]etc 来索引,而不是@$_[0]一个元素数组切片和糟糕的编码实践。通过使用此处的默认值,代码变得更加晦涩难懂$_,我在下面使用命名变量对其进行了澄清。

下面的代码修复了我可以看到的问题。如果您需要任何进一步的帮助,请回来。

use strict;
use warnings;

use DBI;

my %Routings;

my $dbh = DBI-> connect('dbi:ODBC:SQL')
    or die "Couldn't open Databaxe: $DBI::errstr;  stopped";

my $query= $dbh->prepare("SELECT Code, Setup, Process, ProcessID FROM ROUTING");

$query->execute or die "Couldn't execute statement: $DBI::errstr; stopped";

while ( my ($Code, $Setup, $Process, $ProcessID) = $query->fetchrow_array ){
  push @{ $Routings{$Code} }, [ $ProcessID, $Setup, $Process ];
}

for my $Code (keys %Routings) {
  foreach my $triplet ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = $triplet->[0];
    my $SetupMins = $triplet->[1];
    my $ProcessMins = $triplet->[2];
    print "$Code => ($ProcessCodeID, $SetupMins, $ProcessMins)\n";
  }
}

请注意,foreach循环中的分配可以通过一次执行它们来变得更加清晰和简洁。正如我所解释的,@$triplet是一个三元素数组,因此等效赋值可以简单地编码

my ($ProcessCodeID, $SetupMins, $ProcessMins) = @$triplet;

(请谨慎对待此代码,因为如果不进行大量工作来设置测试数据库,我无法对其进行彻底测试,尽管它确实可以在简单的数据集上正常工作。)

于 2012-04-18T04:57:03.483 回答
5

该代码按“代码”对“路由记录”进行分组。


%Routings是一个哈希。它由“代码”键入。每个值都是对数组的引用。这些数组由 自动激活push ( @{ $Routings{$Code} },,它是 的缩写push ( @{ $Routings{$Code} //= [] },

这些数组中的每一个都包含许多“记录”。每个“记录”都是对三个元素(“进程 ID”、“设置”和“进程”)的数组的引用。它们由[ $ProcessID, $Setup, $Process ].

转储看起来像:

{
   $code0 => [
      [ $ProcessID0, $setup0, $Process0 ],
      [ $ProcessID2, $setup2, $Process2 ],
      ...
   ],
   $code1 => [
      [ $ProcessID1, $setup1, $Process1 ],
      [ $ProcessID5, $setup5, $Process5 ],
      ...
   ],
   $code2 => [
      [ $ProcessID3, $setup3, $Process3 ],
      [ $ProcessID4, $setup4, $Process4 ],
      ...
   ],
   ...
}

如果$Code有一个有意义的值——你没有显示它得到一个值——$Routings{$code}将评估为这些数组引用之一。从上面的例子中,

[
   [ $ProcessID0, $setup0, $Process0 ],
   [ $ProcessID2, $setup2, $Process2 ],
   ...
],

@{ ... }向 Perl 表明您要取消对该引用的引用。换句话说,它告诉 Perl 你对数组本身感兴趣。

当您将数组传递给 foreach 时,它会遍历其元素。所以第一次通过循环,$_将持有以下数组引用:

[ $ProcessID0, $setup0, $Process0 ],

第二次,

[ $ProcessID2, $setup2, $Process2 ],

等等

@$_[0](缩写@{ $_ }[0],这是一个不正确使用${ $_ }[0]的,更易读的$_->[0])获取引用数组的第一个元素($ProcessID0)。同样@$_[1]@$_[2]得到$setup0$Process0


当然,然后您继续对数据不做任何事情。你可能打算做

foreach ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = ${$_}[0];
    my $SetupMins     = ${$_}[1];
    my $ProcessMins   = ${$_}[2];
    print("$ProcessCodeID ,$SetupMins, $processMins\n");
}

清理:

foreach ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = $_->[0];
    my $SetupMins     = $_->[1];
    my $ProcessMins   = $_->[2];
    print("$ProcessCodeID, $SetupMins, $processMins\n");
}

又清理了一些:

for ( @{ $Routings{$Code} } ) {
    my ($ProcessCodeID, $SetupMins, $ProcessMins) = @$_;
    print("$ProcessCodeID, $SetupMins, $processMins\n");
}

从技术上讲,你甚至可以做

for ( @{ $Routings{$Code} } ) {
    print(join(', ', @$_), "\n");
}

或者

print(join(', ', @$_), "\n")
   for @{ $Routings{$Code} };
于 2012-04-18T04:00:26.630 回答
5

你对 Perl 的引用了解多少?您可能想查看一些关于Perl 参考的教程

快速参考教程

Perl 的所有三种基本数据结构(标量、数组和散列)都旨在保存单个数据值。例如,我有一个员工数组:

$employee_list[0] = "Bob";
$employee_list[1] = "Carol";
$employee_list[2] = "Ted";
$employee_list[3] = "Alice";

是的,我的数组中有四段数据,但在简单的 Perl 中,每一项都只包含一个值——名字。如果我还想要员工的姓氏、薪水或职位,我该怎么办?在基本的 Perl 数据结构中没有简单的方法可以做到这一点。

引用是一种允许您在 Perl 变量中存储多条数据的方法。让我们看一下 Bob 的完整员工记录:

$employee{FIRST}  = "Bob";
$employee{LAST}   = "Jones";
$employee{PAY}    = "1400";
$employee{PHONE}  = "1234";

现在,我怎样才能将所有这些信息压缩到$employee_list[0]?

Perl 允许你引用这个散列%employee(主要是内存中存储散列的位置。你可以通过在它前面放一个反斜杠来做到这一点:

$employee_list[0] = \%employee;

现在,在那个$employee_list[0]槽中,我引用了一个包含 Bob 的所有员工信息的 Perl 哈希。现在,问题是如何访问这些信息?

我可以通过取消引用来访问我的参考资料中的信息。您可以通过将正确的印记放在参考前面来做到这一点:

$employee_reference = $employee_list[0];
%employee_hash      = %$employee_reference;
print "Employee name is $employee_hash{FIRST} $employee_hash{LAST}\n";

我首先得到引用,然后我可以将它取消引用到一个新的%employee_hash. 一旦我这样做了,我就可以使用散列中的信息。这是很多工作。看$employee_reference。我所做的只是获取参考,所以我可以取消参考。为什么不删掉那一步,直接从$employee_list[0]?

%employee_hash      = %{ $employee_list[0] };
print "Employee name is $employee_hash{FIRST} $employee_hash{LAST}\n";

注意我在我的参考文献周围使用了花括号。花括号有点像方程周围的括号。他们让 Perl 知道首先要做什么。

不过,同样,我并没有真正对%employee_hash. 这只是一个我可以扔散列的地方,所以我可以打印它。为什么不取消引用哈希,并一步获取特定键的值?甚至更好。一步:

print "Employee name is "
   . ${ $employee_list[0] }{FIRST} . " "
   . ${ $employee_list[0] }{LAST} . "\n";

我正在取消引用$employee_list[0]哈希获取该哈希,并在同一步骤中检索特定键的值。请注意,我使用 a$而不是 a %

如您所见,它会很快变得复杂。然而,Perl 为您提供了一种很好的方式来表示这种过于复杂的结构:

print "Employee name is " 
  . $employee_list[0]->{FIRST} . " " 
  . $employee_list[0]->{LAST} . "\n";

->操作员为您取消引用

我建立一个哈希调用%employee_hash只是为了引用它也有点愚蠢。Perl 允许您引用匿名哈希和数组。

$employee_list[0] = { LAST => "Jones", FIRST => "Bob",
    SALARY => 1400, PHONE => "1234" }

花括号用于匿名哈希。方括号用于匿名数组。它们是匿名的,因为它们不引用变量,而只是对哈希或数组的引用。

数据::自卸车

可以想象,这些数据结构会变得相当复杂。例如,我跟踪员工的地址,但地址由街道、城市、州和邮政编码组成。有时,这条街的线路不止一条。而且,如果有多个地址怎么办?哈希或数组引用没有理由不能包含对另一个哈希或数组的引用:

$employee_list[0]->{NAME}->{FIRST} = "Bob";
$employee_list[0]->{NAME}->{LAST}  = "Jones";
$employee_list[0]->{ADDRESS}->[0]->{TYPE} = "Business";
$employee_list[0]->{ADDRESS}->[0]->{STREET}->[0] = "123 Mockingbird Lane";
$employee_list[0]->{ADDRESS}->[0]->{STREET}->[1] = "Tower 2";
$employee_list[0]->{ADDRESS}->[0]->{CITY} = "Beantown";
$employee_list[0]->{ADDRESS}->[0]->{STATE} = "MA";

如您所见,$employee_list[0] 指向对员工哈希的引用。该哈希具有“名称”、“地址”和其他填充数据的键。该NAME字段是对另一个具有两个键的散列的引用:FIRSTLAST。该ADDRESS字段实际上是对地址数组的引用。这些数组条目中的每一个都是对哈希的引用。想象一下尝试调试这个数据结构!

Data::Dumper是一个模块,它将解析最复杂的数据结构并为您打印出来:

use Data::Dumper;

[...]

print "Employee Dump: " . Dumper \@employee . "\n";

这将打印出员工数组中所有员工的整个结构。

如果您不知道是什么@{$value{$key}},您可以轻松地在其上运行转储:

print Dumper $value{$key} . "\n";

解码你的程序

让我们逐行浏览:

%Routings = ();
my $dbh = DBI->connect('dbi:ODBC:SQL')
    or die "Couldn't open Databaxe: $DBI::errstr;  stopped";

您初始化了一个名为的散列%Routings并创建了一个 DBI 对象,该对象表示与您的数据库的连接。connect是在 Perl 中作为 DBI 类的一部分定义的子例程。所有的类都由一堆 Perl 子例程组成,这些子例程对由该类创建的对象进行操作。这些子例程分为构造函数方法。构造函数创建对表示对象的复杂数据结构的引用。方法是可以对该对象进行操作的子程序。想象一下我们的员工记录:

$employee = Person::Employee->new;
$employee->first_name( "Bob" );

第一行从我的班级创建一个$employee对象。Person::Employee$employee对象实际上只是对包含我的员工信息的哈希的引用。所以,我的子程序new是一个构造函数

第二行使用一个名为的子例程first_name,它允许我设置员工的名字。该子例程称为Method或有时称为Member Function

所以,回到程序,我们创建了一个代表我们的数据库连接的对象。如果你愿意,你可以用它Data::Dumper来打印出这个对象的结构,如果这有助于你更好地理解它。它只是对哈希的引用。

my $query= $dbh->prepare("SELECT Code, Setup, Process, ProcessID FROM ROUTING");
$query->execute() or die "Couldn't execute statement: $DBI::errstr; stopped";

我现在准备要执行的 SQL 语句。在我准备好之后,我执行它。执行实际上是对数据库的影响。请注意,my是数据库句柄prepare的一个方法$dbi,但它也是一个构造函数,因为它创建了$query对象。

我使用该$query对象来实际执行我的查询。同样,不要害怕使用Data::Dumper打印出来。

while ( my ($Code, $setup, $process, $processid) = $query->fetchrow_array() ){
    push ( @{ $Routings{$Code} }, [ $ProcessID, $Setup, $Process ] );
}

让我们稍微简化一下:

while ( my @fetched_row = $query->fetchrow_array() ){
    my ($Code, $setup, $process, $processid) = @fetched_row;
    push ( @{ $Routings{$Code} }, [ $ProcessID, $Setup, $Process ] );
}

fetchrow_array是一个从我的查询中获取一行作为列数组的子例程。这个子例程是我上面创建的对象的一个​​方法。$query我所做的只是从我的数据库中获取每一行并将其放入四个 Perl 标量变量中。

最后一行有点棘手。还记得我%Routings初始化的哈希吗?显然,此散列中的每个键都是对值数组的引用。哈希是$Code我在上面提取的,它指向一个由 、 和 组成的三$ProcesssID成员$Setup数组$Process。我们可以像这样重写第三行:

my @temp_array = ($ProcessID, $Setup, $Process);
my @temp_routing_array = @{ $Routings{Code} }; #Dereferencing the $Routing{$Code} array
push( @temp_routing_array, \@temp_array );     #Pushing a reference into my array
$Routing{$Code} = \@temp_routing_array;   #Creating a reference again

[ $ProcessID, $Setup, $Process ]只是创建对匿名数组的引用。这为我们省去了创建@temp_array然后将引用推@temp_array送到我的@temp_routing_array.

而且,当我们这样做时,您的代码中有一个错误。我正在获取$setup$process$processid,但我正在存储(注意变量名的大小写)$Setup$Process$ProcessID

现在,我们进入 foreach 循环和另一个错误。的价值是$Code多少?它没有任何价值,因为变量$Code只存在于上面的while循环中。当你用 声明一个变量时my,一旦你离开一段代码,这个变量的值就会丢失。

use strict;如果您有并且use warnings;在程序的顶部,则可能会捕获此错误以及上述错误。

让我们看看这个循环:

foreach ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = @$_[0];
    my $SetupMins = @$_[1];
    my $ProcessMins = @$_[2];
}

foreach循环使用过时的样式,您假设您正在循环访问$_变量。它令人困惑,大多数人已经学会不使用它。让我们重写它:

my @routing_code_ref_array = @{ $Routings{$Code} };

foreach my $routing_array_ref (@routing_code_ref_array) {
    my @routing_array = @{ $routing_array_ref };

    my $ProcessCodeID  = $routing_array[0];
    my $SetupMins      = $routing_array[1];
    my $ProcessMins    = $routing_array[2];
}

请记住,这$Routings{$Code}是对数组的引用。在我的第一行中,我取消引用它。在原始代码中,取消引用发生在foreach循环中。不仅是$Routings{$Code}一个数组引用,而且该数组中的每个条目都是对另一个数组的引用。它是一个数组数组

因此,我@routing_code_ref_array的每个条目都是对另一个数组的引用,我再次取消引用。现在我只是将每个数组元素的值放入常规的 Perl 标量变量中。

已经足够!

抱歉,解释太长了,但是您有一些涉及引用、类、方法、构造函数、对象和一大堆相当高级的 Perl 主题的代码。以及我指出的一些错误。可以用几个标准 Perl pragma 捕获的错误:use strict;use warnings;.

如果有什么可以带走的,那就是:

  • @$foo{$bar}[4]@{ $foo{bar} }[4]或(更准确地说)${ $foo{bar} }[4]或(更清楚地)之类的东西$foo{bar}->[4]是对更复杂数据结构的引用。基本的 Perl 数据结构一次只能保存一个项目。通过使用对其他数据结构的引用,您可以拥有数组的数组或哈希数组或哈希的哈希或数组的哈希,甚至哈希数组的哈希数组。不要惊慌,并尝试从内到外解析这些事情。有时,如果您可以在多行中处理特别复杂的数据结构,会更容易。
  • 如果你要遇到复杂的结构,Data::Dumper是你的朋友。它将快速揭示这些过于复杂的结构的结构,并可以帮助您调试程序的问题。
  • 在您的程序中使用strict和。warnings这些会发现很多编程问题。正如我所说,我发现两个与局部变量的范围有关,并且在变量名的情况下输入错误。标准化变量名也是一个伟大的想法。这两种方法是 camelCasing 并且只使用下划线和小写字母。这样,您就知道它总是$foo_bar并且从不$Foo_Baror $FooBaror $fooBar。旧标准是驼峰式大小写,第一个字母是小写字母。新标准仅使用小写字母和下划线。
于 2012-04-18T06:01:12.090 回答
0

它实际上并没有尝试数据return——它只是用数据创建变量,然后立即对数据不做任何事情。尝试这个:

foreach ( @{ $Routings{$Code} } ) {
    my $ProcessCodeID = @$_[0];
    my $SetupMins = @$_[1];
    my $ProcessMins = @$_[2];
    print "$Code: $ProcessCodeID, $SetupMins, $ProcessMins\n";
}

除非您在循环中使用变量,否则没有多大意义。

复杂的@{ $foo{$bar} }构造告诉 Perl 将$foo{$bar}其视为一个数组。$foo{$bar}是哈希查找。(见%Routings = ();顶部的?声明并初始化散列。)

Perl 绝对是简洁的,但这样的事情足以让我决定用 Ruby 等新语言编写新代码。这段代码在 Ruby 中可能也不会好很多,而且精通其中任何一种的人都不会真正关心,但您可能希望借此机会在需要维护它们时重新编写工具。

于 2012-04-18T03:15:48.713 回答