【问题标题】:Nested while loop to calculate distance for multiple destinations嵌套while循环来计算多个目的地的距离
【发布时间】:2012-07-26 21:15:20
【问题描述】:

好吧,我现在搞砸了。我想使用严格和警告创建一个合适的脚本(对我来说仍然是一个挑战;)。但现在我完全迷路了。我一直在看这么多的例子,我完全糊涂了。我正在尝试使用纬度/经度计算两点之间的距离。我想我已经用 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);

【问题讨论】:

    标签: perl geolocation while-loop


    【解决方案1】:

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

    全局符号“$infile1”需要在 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,从而结束循环。耶。还要注意下一行中的$infile1split 现在是如何正确的。

      你还需要添加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);
      

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

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

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

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

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

      读起来好多了,不是吗?

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

    • 在第 59 行中,您有一个 print 语句,但您现在正在使用您从未创建过的全局文件句柄 OUTFILE。相反,您需要从顶部使用您的词法文件句柄$OUTFILE。如果我们现在将上面一行中的join 直接添加到打印语句中,并在末尾添加一个\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 了解更多信息。
    • 我从所有die 语句中删除了“\n”字符。如果你在那里放一个新行,它会从输出中删除行号。如果这是有意的,请忽略此建议。以下是Perldoc 对此的看法:

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

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

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

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

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

    • 我将第二个 while 更改为 foreach 循环以遍历新 @file2 数组的元素。为了清楚起见,我确实留下了 $lat2$lon2 变量。您可以省略这些,并直接使用 array(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
    

    【讨论】:

    • 我将为此添加建议。
    • 我认为$elements[4]是城市名称,所以他需要使用ne而不是!=
    • 我仍在修复故障。我马上就会谈到这一点。谢谢@cjm
    • 其他的我稍后再补充。
    • @simbabque 哇,谢谢你的解释!这很有帮助。我真的可以使用它。我特别感谢解释为什么它不起作用!抱歉我没有更详细的谢谢哈哈。我马上去!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多