5

好吧,我现在搞砸了。我想使用严格和警告创建一个合适的脚本(对我来说仍然是一个挑战;)。但现在我完全迷路了。我一直在看这么多的例子,我完全糊涂了。我正在尝试使用纬度/经度计算两点之间的距离。我想我已经用 gis::distance 覆盖了那部分。但问题是我试图找到彼此相距 5000 米以内的目的地。(如果目的地相同,则跳过)。因此,当它找到一个距离另一个目的地 5000m 以内的目的地时,我希望它把它放在第一个文件中的最后一个元素之后。

两个输入文件是相同的,这是它们的外观。这两个文件都有大约 45k 行。

Europe;3;France;23;Parijs;42545;48,856555;2,350976
Europe;3;France;23;Parisot;84459;44,264381;1,857827
Europe;3;France;23;Parlan;11337;44,828976;2,172435
Europe;3;France;23;Parnac;35670;46,4533;1,4425
Europe;3;France;23;Parnans;22065;45,1097;5,1456

假设这些目的地中的 2 个彼此靠近,我试图像这样输出它:

Europe;3;France;23;Parijs;42545;48,856555;2,350976;Parlan;11337;200
Europe;3;France;23;Parisot;84459;44,264381;1,857827;
Europe;3;France;23;Parlan;11337;44,828976;2,172435;
Europe;3;France;23;Parnac;35670;46,4533;1,4425;Parisot;84459;2000;Parnans;22065;350
Europe;3;France;23;Parnans;22065;45,1097;5,1456;

实际结果当然会匹配超过 2 个。在输出文件中添加匹配的目的地、目的地 id 和计算的距离。每个目的地可能有多个匹配项。Pff 这真的很难解释哈哈。正如我所说,我正在使用 strict&warnings 并将错误缩小到最低限度,但仍不完全。这些是错误:

Global symbol "$infile1" requires explicit package name at E:\etc.pl line 17.
Execution of E:\etc.pl aborted due to compilation errors.

这是我到目前为止的代码。我的声明也没有正面或反面。

有人能帮我吗?(也许这不是最有效的方法,但现在它可以帮助我更多地理解 perl,一步一步)

use strict;
use warnings;
use GIS::Distance::Lite qw(distance);

my $inputfile1 = shift || die "Give input!\n";
my $inputfile2 = shift || die "Give more input!\n";
my $outputfile = shift || die "Give output!\n";

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!\n";
open my $INFILE2, '<', $inputfile2  or die "In use/Not found :$!\n";
open my $OUTFILE, '>', $outputfile  or die "In use/Not found :$!\n";

my $maxdist = 5000;
my $mindist = 0.0001;

while ( my @infile1 ){ 
    my @elements = split(";",$infile1);

    my $lat1 = $elements[6];
    my $lon1 = $elements[7];

    $lat1 =~ s/,/./g;
    $lon1 =~ s/,/./g;

    seek my $infile2, 0, 0;

    print "1. $lat1\n";
    print "2. $lon1\n";

    while ( my @infile2 ){
        my @loopelements = split(";",$infile2);

        my $lat2 = $loopelements[6];
        my $lon2 = $loopelements[7];

        $lat2 =~ s/,/./g;
        $lon2 =~ s/,/./g;

        print "3. $lat1\n";
        print "4. $lon1\n";

        my $distance = distance($lat1, $lon1 => $lat2, $lon2);      # Afstand berekenen tussen latlon1 and latlon2

        print "5. $distance\n";

        my $afstand = sprintf("%.4f",$distance);

        print "6. $afstand\n";

        if (($afstand < $maxdist) and (!($elements[4] == $loopelements[4]))){ 
            push (@elements, $afstand,$loopelements[4],$loopelements[5]);
            print "7. $afstand\n";
            } else {
                next;
                }
        }

  @elements = join(";",@elements);  # add ';' to all elements
  print OUTFILE "@elements";
  #if ($i == 10) {last;}
  }
close(INFILE1);
close(INFILE2);
close(OUTFILE);

- - - - - - - - 编辑 - - - - - - -

嗯,我又来了。我一直在查看您更新的代码,这是我的一个非常激烈的版本哈哈。老实说,我只懂了一半。它仍然很有帮助;全部!我决定通过您的改进坚持我的原始脚本设计,但它仍然无法正常工作。如果您不介意,我有几个问题:

我在剧本中做了一些调整。第一个是它现在跳过带有零的纬度,因为这会产生无用的结果。在同一行中,它还会跳过空单元格,这也是无用的。我已经为两个 infiles 做了这个。

哦,在我说元素[4]的地方我提到了元素[5],所以它是数字。所以我把 ne 换成了 != 如果我没记错的话。但我认为我再次创建了一个无限循环,因为它没有循环通过第二个文件。我知道我可能看起来很固执,但我想先了解我的原始脚本,并在我运行我的脚本后立即开始处理你的版本。我认为搜索功能无法正常工作。这是现在的脚本。

use strict;
use warnings;
use GIS::Distance::Lite qw(distance);

my $inputfile1 = shift || die "Give input!\n";
my $inputfile2 = shift || die "Give more input!\n";
my $outputfile = shift || die "Give output!\n";

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!\n";
open my $INFILE2, '<', $inputfile2  or die "In use/Not found :$!\n";
open my $OUTFILE, '>', $outputfile  or die "In use/Not found :$!\n";

my $maxdist = 3000;
my $mindist = 0.0001;

while (my $infile1 = <$INFILE1> ){
  chomp $infile1;
  my @elements = split(";",$infile1);

  print "1. $elements[6]\n";
  print "2. $elements[7]\n";

  my $lat1 = $elements[6];
  my $lon1 = $elements[7];

if ((($lat1 and $lon1) ne '0') and (!($lat1 and $lon1) eq "")){
        $lat1 =~ s/,/./;
        $lon1 =~ s/,/./;
        print "lat1: $lat1\n";
        print "lon1: $lon1\n";  
        } else {
            next;
            }

  print "3. $lat1\n";
  print "4. $lon1\n";

  seek $INFILE2, 0, 0;

  while ( my $infile2 = <$INFILE2> ){
    chomp $infile2;
    my @loopelements = split(";",$infile2);

print "5. $elements[6]\n";
print "6. $elements[7]\n";

    my $lat2 = $loopelements[6];
    my $lon2 = $loopelements[7];

if ((($lat2 and $lon2) ne '0') and (!($lat2 and $lon2) eq "")){
        $lat2 =~ s/,/./;
        $lon2 =~ s/,/./;
        print "lat2: $lat1\n";
        print "lon2: $lon1\n";  
        } else {
            next;
            }

my $distance = distance($lat1, $lon1 => $lat2, $lon2);      # Afstand berekenen tussen latlon1 and latlon2

print "7. $distance\n";

my $afstand = sprintf("%.4f",$distance);

print "8. $afstand\n";

if ($afstand < $maxdist && $elements[4] != $loopelements[4]){ 
  push (@elements, $afstand, $loopelements[4],$loopelements[5]);
  print "9. $afstand\n";
    } else {
        next;
        }
  }
print $OUTFILE join(";",@elements), "\n";
}

close($INFILE1);
close($INFILE2);
close($OUTFILE);
4

1 回答 1

5

你已经很好了。让我们看一下您的错误消息。

全局符号 "$infile1" 需要在 E:\etc.pl 第 17 行显示包名。

这个很简单。在 Perl 中,所有变量名都区分大小写。在顶部,您创建了一个词法变量 $INFILE1。稍后我将详细讨论词汇。

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!\n";

在这里,您可以将其全部大写,这没关系。如果它可以帮助您记住它是一个词法文件句柄(文件句柄曾经是全局的并且命名为INFILE1),那么您可以这样做。稍后(在第 17 行)您使用$infile1.

my @elements = split(";",$infile1);

您尚未声明该变量(使用my),因此会引发此错误。但这还不是全部。

我相信您正在尝试从该文件句柄中读取。但这不起作用。我将逐步解释这一点。- 事实上,你已经建立了一个无限循环,但你还没有意识到它。

    while ( my @infile1 ){ 

这个while循环不会停止。曾经。@infile1with的声明my总是返回一个真值,因为它总是有效的。所以你永远不会打破循环。

  • 我猜您正在尝试逐行读取文件。那么让我们看看我们如何做到这一点:

    while (my $infile1 = <$INFILE1> ){ 
      my @elements = split(";",$infile1);
    

    您需要像这样从文件中读取。while现在,只要文件句柄返回一行,循环头部中的赋值就会为真。一旦它在文件的末尾,它将返回undef,从而结束循环。耶。还要注意你 $infile1在下一行的splitnow 是如何正确的。

    您还需要添加chomp到组合中,因为文件末尾有换行符:

    while (my $infile1 = <$INFILE1> ){ 
      chomp $infile1;
      my @elements = split(";",$infile1);
    
  • 接下来是seek线。这看起来像您想从头开始读取第一个文件的每一行的第二个文件。这在某种程度上是有道理的,但效率很低。我稍后再谈。你确实需要改变my。您不必在此处创建新变量。另外,请使用正确的名称:

    seek $INFILE2, 0, 0;
    
  • 让我们修复第二个while循环:

    while (my $infile2 = <$INFILE2>){
      chomp $infile2;
      my @loopelements = split(";",$infile2);
    
  • 我注意到的下一件事是在第 42 行:

    my $distance = distance($lat1, $lon1 => $lat2, $lon2);
    

    别担心,这里没有任何问题。我只想指出,这=>是另一种写逗号 ( ,) 的方式。它有时被称为胖逗号,它使阅读更容易,例如哈希分配。

  • 在第 50 行中,您已经知道了距离。

    if (($afstand < $maxdist) and (!($elements[4] == $loopelements[4]))){     
    

    and通常用于进行错误检查。请参阅 perldoc 了解原因。你应该&&改用。因为它具有更高的优先级,所以可以省略括号。您还可以更改!($a == $b)构造以使用!=运算符。由于它包含城市名称,而且这是一个字符串而不是数字,因此您需要使用ne,它与eq. 所以这一行现在变成了:

    if ($afstand < $maxdist && $elements[4] ne $loopelements[4]){
    

    读起来好多了,不是吗?

  • 在第 58 行中,您join将数组@elements分配给它自己。这很奇怪。它将用一个只有一个元素的新数组替换该数组 - 连接的字符串。让我们把这条线留到下一个项目符号,然后再看它。

  • 在第 59 行,您有一个print语句,但您现在使用的是一个OUTFILE从未创建过的全局文件句柄。相反,您需要从顶部使用您的词法文件句柄,$OUTFILE. 如果我们现在将join上一行的 from 直接添加到 print 语句中,并\n在末尾添加一个换行符,则该行变为:

    print $OUTFILE join(";",@elements), "\n";
    
  • 现在只剩下最后一部分:您需要关闭文件句柄,但您再次使用全局句柄。改用你的词汇:

    close($INFILE1);
    close($INFILE2);
    close($OUTFILE);
    

现在完整的代码如下所示:

use strict;
use warnings;
use GIS::Distance::Lite qw(distance);

my $inputfile1 = shift || die "Give input!\n";
my $inputfile2 = shift || die "Give more input!\n";
my $outputfile = shift || die "Give output!\n";

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!\n";
open my $INFILE2, '<', $inputfile2  or die "In use/Not found :$!\n";
open my $OUTFILE, '>', $outputfile  or die "In use/Not found :$!\n";

my $maxdist = 5000;
my $mindist = 0.0001;

while (my $infile1 = <$INFILE1> ){
  chomp $infile1;
  my @elements = split(";",$infile1);

  my $lat1 = $elements[6];
  my $lon1 = $elements[7];

  $lat1 =~ s/,/./g;
  $lon1 =~ s/,/./g;

  print "1. $lat1\n";
  print "2. $lon1\n";

  seek $INFILE2, 0, 0;

  while ( my $infile2 = <$INFILE2> ){
    chomp $infile2;
    my @loopelements = split(";",$infile2);

    my $lat2 = $loopelements[6];
    my $lon2 = $loopelements[7];

    $lat2 =~ s/,/./g;
    $lon2 =~ s/,/./g;

    print "3. $lat1\n";
    print "4. $lon1\n";

    my $distance = distance($lat1, $lon1 => $lat2, $lon2);      # Afstand berekenen tussen latlon1 and latlon2

    print "5. $distance\n";

    my $afstand = sprintf("%.4f",$distance);

    print "6. $afstand\n";

    if ($afstand < $maxdist && $elements[4] ne $loopelements[4]){ 
      push (@elements, $afstand,$loopelements[4],$loopelements[5]);
      print "7. $afstand\n";
    } else {
      next;
    }
  }

  print $OUTFILE join(";",@elements), "\n";
}

close($INFILE1);
close($INFILE2);
close($OUTFILE);

现在看看你的算法的工作方式:首先读取完整的第二个文件,然后在每次迭代中与第一个文件进行比较会更有效。这样,您只需读取文件一次。

use strict;
use warnings;
use GIS::Distance::Lite qw(distance);
use feature qw(say);

my $inputfile1 = shift || die "first file missing";
my $inputfile2 = shift || die "second file missing";
my $outputfile = shift || die "output file missing!";

# Read the second file first
my @file2; # save the lines of INFILE2 as array refs
open my $INFILE2, '<', $inputfile2  or die "In use/Not found :$!";
while ( my $infile2 = <$INFILE2> ){ 
  chomp $infile2;
  my @loopelements = split(/;/, $infile2);

  $loopelements[6] =~ y/,/./;
  $loopelements[7] =~ y/,/./;

  push @file2, \@loopelements;
}
close($INFILE2);

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!";
open my $OUTFILE, '>', $outputfile  or die "In use/Not found :$!";

my $maxdist = 5000;
my $mindist = 0.0001;

while (my $infile1 = <$INFILE1> ){
  chomp $infile1;
  my @elements = split(";",$infile1);

  my $lat1 = $elements[6];
  my $lon1 = $elements[7];

  $lat1 =~ y/,/./;
  $lon1 =~ y/,/./;

  say "1. $lat1";
  say "2. $lon1";

  FILE2: foreach my $loopelements ( @file2 ){
    my ($lat2, $lon2) = @$loopelements[6, 7];

    say "3. $lat2";
    say "4. $lon2";

    my $distance = distance($lat1, $lon1 => $lat2, $lon2);      # Afstand berekenen tussen latlon1 and latlon2

    say "5. $distance";

    my $afstand = sprintf("%.4f",$distance);

    say "6. $afstand";

    if ($afstand < $maxdist && $elements[4] ne $$loopelements[4]){ 
      push (@elements, $afstand, $$loopelements[4], $$loopelements[5]);
      say "7. $afstand";
    } else {
      next FILE2;
    }
  }

  say $OUTFILE join(";",@elements);
}

close($INFILE1);
close($OUTFILE);

现在,让我们看看我改变了什么。

  • 首先,我添加use feature qw(say)到顶部。say与 相同print,但增加了一个新行。这样可以节省一些打字。另请参阅feature以获取更多信息。
  • 我从所有die语句中删除了“\n”字符。如果你在那里放一个新行,它会从输出中删除行号。如果这是有意的,请忽略此建议。这是Perldoc对此的看法:

    如果 LIST 的最后一个元素没有以换行符结尾,则还会打印当前脚本行号和输入行号(如果有),并提供换行符。

  • 最重要的部分是我对算法所做的更改。我将while第二个文件的循环移到另一个while循环之外,到程序的顶部。该文件被吞入数组@file2中。每个元素都包含一个数组 ref,其中包含该行的字段。逗号已更改为句号。

    我将s///替换运算符更改为y///(短tr///)音译运算符。因为你只改变一个标志,这就足够了。它也更快。即使您将正则表达式替换留在其中,您也不需要/g修饰符,因为浮点数只有一个逗号,因此不必进行多次替换。

    现在所有这些事情只为file2完成一次。当完成 40k+ 次时,这可以节省大量的计算时间。

  • 我更改了错误消息的措辞,以便更好地理解它们。那是偏好。你不必那样做。

  • 我将第二个更改whileforeach循环以迭代新@file2数组的元素。为了清楚起见,我确实留下了$lat2and $lon2vars。您可以省略这些,并直接使用 array(ref) 元素。在作业中,我使用了一个数组切片将其放入一行。

  • 由于$loopelementsreplaces并且它是一个数组 ref,因此我们需要使用now@loopelements访问存储在其中的数据。$$loopelements[$index]

我希望这可以帮助您理解为什么我做出了某些改进。

请记住,在 Perl 中有不止一种方法可以做到这一点——这是一件好事。很少有正确的方法,但通向目标的方法往往很多。有些比其他的更有效,而其他的有时更容易维护。诀窍是在这两种情况之间找到适当的平衡。


更新:

这是我使用的输入文件。您将需要它们来比较结果。

文件 1.csv

Europe;3;France;23;Parijs;42545;48,856555;2,350976
Europe;3;France;23;Parisot;84459;44,264381;1,857827
Europe;3;France;23;Parlan;11337;44,828976;2,172435
Europe;3;France;23;Parnac;35670;46,4533;1,4425
Europe;3;France;23;Parnans;22065;45,1097;5,1456

文件 2.csv

Europe;3;France;23;Parlan;11337;44,828976;2,172435
Europe;3;France;23;Parnac;35670;46,4533;1,4425
Europe;3;France;23;Parnans;22065;45,1097;5,1456
Europe;3;France;23;Parijs;42545;48,856555;2,350976
Europe;3;France;23;Parisot;84459;44,264381;1,857827
于 2012-07-24T07:36:34.347 回答