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

Jan*_*Jan 5 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
Run Code Online (Sandbox Code Playgroud)

假设这些目的地中有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;
Run Code Online (Sandbox Code Playgroud)

当然,实际结果将匹配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.
Run Code Online (Sandbox Code Playgroud)

这是我到目前为止的代码.我也没有对我的宣言做出正面或反面.

有人能帮我吗?(也许这不是最有效的方法,但现在它帮助我逐步了解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);
Run Code Online (Sandbox Code Playgroud)

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

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

我在剧本中做了一些调整.第一个是它现在跳过零的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);
Run Code Online (Sandbox Code Playgroud)

sim*_*que 5

你已经很好了.我们来看看你的错误信息.

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

这个很简单.在Perl中,所有变量名都区分大小写.在顶部,您创建一个词法变量 $INFILE1.我会在一分钟内更多地谈论词法.

open my $INFILE1, '<', $inputfile1  or die "In use/Not found :$!\n";
Run Code Online (Sandbox Code Playgroud)

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

my @elements = split(";",$infile1);
Run Code Online (Sandbox Code Playgroud)

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

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

    while ( my @infile1 ){ 
Run Code Online (Sandbox Code Playgroud)

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

  • 我想你是想逐行读取文件.那么让我们看看我们如何做到这一点:

    while (my $infile1 = <$INFILE1> ){ 
      my @elements = split(";",$infile1);
    
    Run Code Online (Sandbox Code Playgroud)

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

    您还需要添加chomp到混合中,因为文件末尾有新的行字符:

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

    seek $INFILE2, 0, 0;
    
    Run Code Online (Sandbox Code Playgroud)
  • 让我们修复第二个while循环:

    while (my $infile2 = <$INFILE2>){
      chomp $infile2;
      my @loopelements = split(";",$infile2);
    
    Run Code Online (Sandbox Code Playgroud)
  • 接下来我注意到的是第42行:

    my $distance = distance($lat1, $lon1 => $lat2, $lon2);
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 在第50行,你已经有了距离.

    if (($afstand < $maxdist) and (!($elements[4] == $loopelements[4]))){     
    
    Run Code Online (Sandbox Code Playgroud)

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

    if ($afstand < $maxdist && $elements[4] ne $loopelements[4]){
    
    Run Code Online (Sandbox Code Playgroud)

    读它好多了,不是吗?

  • 在第58行,您join的数组@elements并将其分配给自己.这很奇怪.它将用一个只有一个元素的新数组替换数组 - 连接的字符串.让我们离开那条线直到下一颗子弹然后再看看它.

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

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

    close($INFILE1);
    close($INFILE2);
    close($OUTFILE);
    
    Run Code Online (Sandbox Code Playgroud)

完整的代码现在看起来像这样:

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);
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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

  • 首先,我加入use feature qw(say)了顶部.say是相同的print,但添加了一个新的行.这节省了一些打字.另请参阅feature更多信息.
  • 我从所有die语句中删除了"\n"字符.如果在那里添加一个新行,它将从输出中删除行号.如果是这样的话,请忽略这个建议.以下是Perldoc对此的评价:

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

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

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

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

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

  • 我将第二个while更改为foreach循环以迭代新@file2数组的元素.为了清晰起见,我确实离开了$lat2$lon2vars.您可以省略这些,并直接使用数组(ref)元素.在赋值中,我使用了一个数组切片将它放入一行.

  • 由于$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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)