5

わかりました私は今かなりひどくめちゃくちゃです。strict と warnings を使用して適切なスクリプトを作成したかった (私にとってはまだ挑戦です;)。しかし今、私は完全に迷っています。私は非常に多くの例を見てきましたが、完全に混乱しています。私がしようとしているのは、緯度/経度を使用して 2 点間の距離を計算することです。その部分は gis::distance でカバーしていると思います。しかし、問題は、互いに 5000 m 以内にある目的地を見つけようとしていることです。(宛先が同じ場合はスキップ)。したがって、別の目的地から 5000 m 以内にある目的地が見つかった場合は、最初のファイルの最後の要素の後に配置する必要があります。

どちらの入力ファイルも同じです。次のように表示されます。どちらのファイルにも約 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、および計算された距離が追加されます。各目的地に複数の一致が存在する可能性があります。うーん、これを説明するのは本当に難しいです(笑)。私が言ったように、私は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);

- - - - - - - - 編集 - - - - - - -

さて、また来ました。私はあなたの更新されたコードを見てきましたが、これは私のかなり強烈なバージョンです。私は正直言って、半分しか理解していないと言わざるを得ません。それでもかなり役に立ちます。それのすべて!私はあなたの改善で元のスクリプト設計に固執することにしましたが、まだ機能していません. 差し支えなければ、いくつか質問があります。

スクリプトにいくつかの調整を加えました。1 つ目は、latlon をゼ​​ロでスキップするようになったことです。これは、役に立たない結果になるためです。同じ行で、空のセルもスキップしますが、これも役に立ちません。私は両方のinfilesに対してこれを行いました。

ああ、要素[4]は要素[5]だと言ったので、数字になります。だから、私が間違っていなければ、!= の ne を切り替えました。しかし、2 番目のファイルをループしていないため、再び無限ループを作成したと思います。私は頑固に見えるかもしれませんが、最初に元のスクリプトを理解し、私のバージョンを実行したらすぐにあなたのバージョンに取り組みたいと思っていました. シーク機能が正常に動作していないと思います。これが今のスクリプトです。

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常に機能するため、常に真の値を返します。したがって、ループを壊すことはありません。

  • ファイルを1行ずつ読み取ろうとしていると思います。それでは、それを行う方法を見てみましょう。

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

    このようなファイルから読み取る必要があります。これで、ループの先頭での代入はwhile、ファイル ハンドルから返された行がある限り true になります。ファイルの最後になると、 が返さundefれ、ループが終了します。わーい。$infile1また、次の行のsplitnow がどのように正しいかにも注意してください 。

    chompファイルの最後に改行文字があるため、ミックスに追加する必要もあります。

    while (my $infile1 = <$INFILE1> ){ 
      chomp $infile1;
      my @elements = split(";",$infile1);
    
  • 次はseekラインです。これは、最初のファイルの行ごとに 2 番目のファイルを最初から読みたいようです。それはある意味では理にかなっていますが、非常に非効率的です。それについては後で話します。ただし、変更する必要がありmyます。ここで新しい変数を作成する必要はありません。また、正しい名前を使用してください。

    seek $INFILE2, 0, 0;
    
  • while代わりに、2 番目のループを修正しましょう。

    while (my $infile2 = <$INFILE2>){
      chomp $infile2;
      my @loopelements = split(";",$infile2);
    
  • 次に気づいたのは 42 行目です。

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

    心配しないでください。ここには何も問題はありません。=>カンマ ( ) のもう 1 つの書き方であることに注意してください,ファット コンマと呼ばれることもあり、ハッシュ割り当てなどを読みやすくします。

  • 50行目で、すでに距離を取得しています。

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

    and通常、エラーチェックを行うために使用されます。理由については、perldoc を参照してください。&&代わりに使用する必要があります。優先順位が高いため、括弧を省略できます。代わりに演算子!($a == $b)を使用するように構成を変更することもできます。しかし、それは都市名を保持しており、それは数値ではなく文字列であるため、 の反対であるを使用する必要があります。したがって、この行は次のようになります。!=neeq

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

    読んだ方がずっといいですよね?

  • 58 行目でjoin、配列@elementsを自分自身に割り当てます。それはかなり奇妙です。配列を、要素を 1 つだけ持つ新しい配列 (結合された文字列) に置き換えます。その行は次の箇条書きまで残して、それから見てみましょう。

  • 59 行目にprintステートメントがありますが、作成したことのないグローバル ファイル ハンドルOUTFILEを使用しています。代わりに、レキシカル ファイル ハンドルを先頭から使用する必要があります$OUTFILEjoin上記の行から 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);

次に、アルゴリズムの動作方法について説明します。最初に完全な 2 番目のファイルを読み取ってから、各反復で最初のファイルと比較する方がはるかに効率的です。そうすれば、ファイルを一度だけ読み取る必要があります。

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 の最後の要素が改行で終わらない場合、現在のスクリプト行番号と入力行番号 (存在する場合) も出力され、改行が提供されます。

  • 最も重要な部分は、私が行ったアルゴリズムの変更です。while2 番目のファイルのループを、他のループの外側while、プログラムの先頭に移動しました。ファイルは配列に丸呑みされます@file2。各要素は、行のフィールドを持つ配列 ref を保持します。カンマはすでに完全な一時停止記号に変更されています。

    s///置換演算子をy///(の短い) 文字変換演算子に変更しましたtr///。記号を 1 つ変更するだけなので、これで十分です。また、高速です。正規表現の置換をそのままにしても、修飾子は必要ありません/g。フロートにはコンマが 1 つしかないため、複数の置換を行う必要がないからです。

    これで、これらすべてが file2 に対して 1 回だけ実行されます。これにより、40,000 回以上実行すると、計算時間が大幅に節約されます。

  • エラー メッセージの文言を変更して、よりよく理解できるようにしました。それが好みです。あなたはそれをする必要はありません。

  • while2 番目をforeachループに変更して、新しい@file2配列の要素を反復処理しました。$lat2わかりやすくするために、と$lon2vars を残しました。これらを省略して、array(ref) 要素を直接操作できます。割り当てでは、配列スライスを使用して 1 行にまとめました。

  • $loopelementsは置換され、配列参照であるため、今@loopelementsを使用して格納されているデータにアクセスする必要があります$$loopelements[$index]

これが、私が特定の改善を行った理由を理解するのに役立つことを願っています.

Perl には複数の方法があることを覚えておいてください。これは良いことです。正しい道はめったにありませんが、多くの場合、目標につながる多くの方法があります。他のものよりも効率的なものもあれば、維持しやすいものもあります。秘訣は、これら 2 つのケースの間で適切なバランスを見つけることです。


アップデート:

これが私が使用した入力ファイルです。結果を比較するためにそれらが必要になります。

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
于 2012-07-24T07:36:34.347 に答える