嵌套while循环计算多个目的地的距离

时间:2012-07-24 06:56:12

标签: perl geolocation while-loop

哦,我现在搞得很糟糕。我想用严格和警告创建一个合适的脚本(对我来说仍然是一个挑战)。但现在我完全迷失了。我一直在看这么多的例子,我很困惑。我正在尝试使用lat / lon计算2点之间的距离。我想我有那个部分覆盖了gis :: distance。但问题是我试图找到彼此相距5000米的目的地。 (如果目的地相同则跳过)。因此,当它找到距离另一个目的地5000米的目的地时,我希望它将它放在第一个文件中的最后一个元素之后。

两个输入文件都是相同的,这是它们的外观。两个文件都有大约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个。在outputfile中,添加匹配的目标,目标ID和计算的距离。每个目的地可能有多个匹配项。这真的很难解释哈哈。正如我所说,我正在使用严格和警告,并将错误范围缩小到最小但仍然不完全。这些是错误:

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);

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

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

我在剧本中做了一些调整。第一个是它现在跳过零的latlons,因为这会产生无用的结果。在同一行中,它也会跳过空单元格,这也是无用的。我已经为这两个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);

1 个答案:

答案 0 :(得分:5)

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

  

全球符号&#34; $ infile1&#34;需要在E:\ etc.pl第17行显式包名。

这个很简单。在Perl中,所有variable names都区分大小写。在顶部,您可以创建lexical变量$INFILE1。我会在一分钟内更多地谈论词汇。

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

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

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

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

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

    while ( my @infile1 ){ 

while循环不会停止。永远。 @infile1my的声明始终返回真值,因为它始终有效。所以你永远不会打破循环。

  • 我猜你是在尝试逐行阅读文件。所以,让我们看看我们如何做到这一点:

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

    你需要像这样读取文件。现在只要从文件句柄返回一行,while循环头中的赋值就会成立。一旦它在文件的末尾,它将返回undef,从而结束循环。好极了。另请注意,$infile1下一行中的split现在是正确的。

    您还需要将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直接添加到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)添加到了顶部。 sayprint相同,但会添加新行。这节省了一些打字。有关详情,请参阅feature
  • 我删除了&#34; \ n&#34;所有die语句中的字符。如果在那里添加一个新行,它将从输出中删除行号。如果是这样的话,请忽略这个建议。这是Perldoc对此所说的内容:

      

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

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

    我将s///替换运算符更改为y///(短tr///)音译运算符。由于您只更改了一个符号,这就足够了。它也更快。即使您不使用正则表达式替换,也不需要/g修饰符,因为浮点数只有一个逗号,所以它不需要进行多次替换。

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

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

  • 我将第二个while更改为foreach循环,以迭代新@file2数组的元素。为了清楚起见,我确实离开了$lat2$lon2 vars。您可以省略这些,并直接使用数组(ref)元素。在作业中,我使用array slice将其放入一行。

  • 由于$loopelements取代@loopelements并且它是数组引用,我们现在需要使用$$loopelements[$index]访问存储在其中的数据。

我希望这有助于您了解我为何做出了一些改进。

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


<强>更新

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

file1.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

file2.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