• ベストアンサー

Perlの初心者です。2重ループの方法で困ってます。

ある二つのファイル(moto1.csvとmoto2.csv)の2番目のフィールドが おなじときに二つのファイルの中身をあわせて別のファイル(kekka.csv)を 作る作業をしています。 下記のソースで※2の場所で何回もファイルをオープンさせるととても重いので ※1でファイルを一回だけオープンさせて処理しようと思ったら。 内側のループ(moto2_Log)が一回しか処理されないので困っております。 何かよいアイデアがありましたらよろしくお願いします。 open(moto1_Log,"< moto1.csv"); open(kekka_Log,"> kekka.csv"); ※1open(moto2_Log,"< moto2.csv"); while( <moto1_Log> ) { chop; @moto1_List=split(/,/); ※2 #open(moto2_Log,"< moto2.csv"); while( <moto2_Log> ) { chop; @moto2_List=split(/,/); if($moto1_[1] eq $moto2_List[1]){ print kekka_Log $S_List[0]; print kekka_Log ","; print kekka_Log $S_List[1]; print Export_Log ","; print Export_Log $S_List[2]; print Export_Log ","; print Export_Log $S_List[3]; print Export_Log ":"; print Export_Log $E_List[0]; print Export_Log ","; print Export_Log $E_List[1]; print Export_Log ","; print Export_Log $E_List[2]; print Export_Log ","; print Export_Log $E_List[3]; print Export_Log "\n"; #改行コード continu; } } } close (moto2_Log); close(kekka_Log); close(moto1_Log);

  • Perl
  • 回答数3
  • ありがとう数6

質問者が選んだベストアンサー

  • ベストアンサー
  • Fooky
  • ベストアンサー率71% (59/82)
回答No.2

原因についてはa-kumaさんが仰ってる通りですので、 代替案を挙げたいと思います。 「何回もファイルをオープンさせるととても重いので」という ことですが、ファイルを一々巻き戻す(seekする)現在の方法でも、 実行時間としてはほとんど変わらないと思いますよ。 openにかかる時間が中身をリードする時間と比較して、 誤差以上に意味のある時間になるとは思えません。 同様のプログラムをつくって、moto1.csvとmoto2.csvとして 500行のテキストファイル(具体的には500行に切り詰めた Linuxのシステムログ/var/log/messagesファイル) を使って実験したところ、 一々オープンする方法の実行時間(47.63秒)は、 巻き戻す方法の実行時間(47.28秒)と比較して、 たった0.7%しか増加しませんでした。 この実験では、moto2.csvは全てキャッシュに乗っていますが、 キャッシュに乗りきらないほど大きなファイルになったとしても、 さして結果は変わらないと思います。 そこで代替案ですが、一旦、片方のファイルを全て 配列に読み込んではどうでしょう? open(moto2_Log, "<moto2.csv"); my @moto2_List; while(<moto2_Log>){  chomp;  my @cols = split(/,/);  push(@moto2_List, \@cols); } close(moto2_Log); open(moto1_Log, "<moto1.csv"); open(kekka_Log, ">kekka.csv"); while(<moto1_Log>){  chomp;  my @moto1_List = split(/,/);  foreach $m2lst ( @moto2_List ){   if( $moto1_List[1] eq $m2lst->[1] ){    print kekka_Log ...   }  } } close(kekka_Log); close(moto1_Log); 上の実験と同じ500行のテキストファイルに対して、 この方法だと実行時間は約5分の1の、10.17秒と なりました。まあ、ファイルアクセスの時間だけ じゃなくて、splitの回数も減ってるので、その 影響もあるんでしょう。 ところで、このアルゴリズムだと、moto1.csvの 中の各行とmoto2.csvの中の全行を照合してますが、 それはそれで合ってるんでしょうか? > 2番目のフィールドが > おなじときに二つのファイルの中身をあわせて別のファイル(kekka.csv)を > 作る という辺りから、moto1.csvの各行と、moto2.csvで 対応する(ファイルの先頭から数えた行数が同じ)行を 照合することを意図しているようにも読めるんですが…。

arowana
質問者

お礼

ありがとうございます。 確かに時間はさほど変わりませんでした。 どうやって時間を短縮さえ酔うと悩んでいたところです。 とても参考になります。

その他の回答 (2)

  • leaz024
  • ベストアンサー率75% (398/526)
回答No.3

 2件の回答がついていますが、解決はされたのでしょうか?  質問に書かれたソースと、全く同じ動作をするコードを書いてみました。参考にしてみてください。   my %moto2;   open IN, 'moto2.csv';   while (<IN>) {     chomp;     my $_2nd = (split(/,/))[1];     $moto2{$_2nd} = $_ unless defined $moto2{$_2nd};   }   close IN;   open OUT, '>kekka.csv';   open IN, 'moto1.csv';   while (<IN>) {     chomp;     my $_2nd = (split(/,/))[1];     print OUT "$_, $moto2{$_2nd}\n" if defined $moto2{$_2nd};   }   close IN;   close OUT; ○moto2.csvの2番目のデータをキーにしたハッシュを作成します。  値はそのデータの行全体(改行は抜いたもの)にします。後で結局カンマ区切りの合成をするので、そのまま使うわけです。 ○moto1.csvを開いて1行ずつ読み、2番目のデータを取り出します。  このデータを%moto2のKEYにして値があれば、moto1とmoto2で全く同じデータが存在するわけです。 ○kekka.csvには、2番目に同じデータがある行の全項目をカンマ区切りにしたデータを入れるので、split前のデータから改行を抜いたもの同士をカンマを挟んで書き込みます。  (%moto2のVALUEには、moto2.csvから改行を抜いたデータが入れてある)

  • a-kuma
  • ベストアンサー率50% (1122/2211)
回答No.1

二つ目のファイルを一回読み込んだら、読み込み位置がファイルの一番お尻に あるからですね。 二つ目のファイルを処理しおわったら、巻き戻しましょう。 open(moto1_Log, ...); open(moto2_Log, ...); while ( <moto1_Log> ) {   ...   while ( <moto2_Log> ) {     ...   }   seek(moto2_Log, 0, 0);  # ← これ } ってな感じ。 # perl は良く知らないんですけど、多分OK

arowana
質問者

お礼

seekですね 理由は何と無く分かったいました。 戻し方が解りませんでした(リファレンスだけだと探すのが大変です) ありがとうございます。

関連するQ&A

  • Perlプログラムの変更方法

    メーリングリストのようなものをPerlで作って いるのですが、text.txtを一回だけ読み込むような プログラムに変更したいのですが、どこをどのように 変更すれば良いのでしょうか。 ================プログラム本体===================== #!/usr/bin/perl $wunderprize='100% genuine faux diamond'; open LIST,"list.txt"; while($name =<LIST>){ chomp($name); @f=split(/\s+/,$name); if($f[2] ne""){ open MAIL, "|mail $f[2]"; open TEXT,"text.txt"; while($line=<TEXT>){ $line=~ s/<FIRST>/$f[0]/g; $line=~ s/<FAMILY>/$f[1]/g; $line=~ s/<SUCKER>/$f[0] $f[1]/g; $line=~ s/<TRINKET>/fabulous $wunderprize/g; print MAIL "$line\n"; } close MAIL; close TEXT; } } close LIST; ==============text.txt======================== Dear <FIRST>, You have been chosen to win a brand new <TRINKET>!Free! Could you use another <TRINKET> in the <FAMILY> household? Yes <SUCKER>, I bet you could! Just respond by... ===============list.txt======================= 名前 名字 メールアドレス (実際はきちんとしたものを打ち込みます)

    • ベストアンサー
    • Perl
  • perl初心者ですが、プログラム作成で行き詰ってしまいました。

    perl初心者ですが、プログラム作成で行き詰ってしまいました。 参考書を読みあさり、色々とググってみたのですが、どうしても組み立てられません。知識のある方ご回答よろしくお願いします。 一つ目は: 英語の文章を記憶したファイルを指定し,その内容を読み込み,文章内に含まれる単語を出現頻度の降順で表示させるプログラムを作成すること 「1位:・・・(・・回)」 「2位:・・・(・・回)」 ... なお,一つの単語が二行にまたがることはないものとする。 二つ目は: テストの成績データを記憶したCSV ファイルを指定し,その内容を読み込み,各学生の総合点と順位,各科目の最高点,最低点,平均点を記憶したCSVファイルを書き出すプログラムを作成せよ。 なお,入力用のファイルと出力用のファイルはキーボード入力で指定するものとする。 また,異なる学生数および科目数のCSV ファイルを読み込んでも正しく動作するようにすること。 卒業のかかったレポートで、この二つを完成させないと先に進めないのです。 本当に図々しい質問で大変申し訳ありません。知識のある方どうかご教授ねがいます。 *ひとつめに関しては: open (FILE, 'perl.txt') or die "$!"; while (my @array = <FILE>){ foreach (@array) { $count{ $_ }++ ; } @rank = sort { $count{ $b } <=> $count{ $a } } keys %count ; foreach (0..$#rank) { $kagi = $rank[ $_ ]; $kaisu = $count{ $kagi } ; print $_+1, "位:$kagi($kaisu 回)\n" ; } } のように書いたところで行き詰ってしまいました。 二つ目は: open (FILE, 'data.txt') chomp(@data = <FILE>) ; $i= 1 ; # 行番号を記憶する変数 for (@data) { # 各行に対して以下を繰り返す. # 各行の数値を配列の要素として記憶する. @list = split(/,/, $_) ; # サブルーチンを呼び出して,結果を表示 print "$i行目:" ; print "数値:",&cardinality(@list),"個," ; print "最大値:",&maximum(@list),"," ; print "最少値:",&minimum(@list),"," ; print "合計:",&summation(@list),"," ; print "平均:",&average(@list),"\n" ; # 行番号の更新 $i++ ; } とまで書き、もう何が何だか訳わからなくなってしまいました。

    • ベストアンサー
    • Perl
  • perl:パターンマッチを使ったifの条件

    perlでパターンマッチを使ったifの条件が必ずTRUEになってしまいます。 以下は条件です。二つのファイルがあって、片方のファイルのある列の文字列と別のファイルのある列の文字列が一致したら一致した行の1列目を出力するというプログラムです。 perl v5.12.3 『oct_gene.csv』は以下のような2列のファイルで、2列目であるalphabetの群は空白で区切られています。文字コードはASCIIです。 1 zinc finger protein of the cerebellum 3 0 stathmin-like 2 . . . . 『RNA.csv』は以下のような3列のファイルで、3列目のalphabet群は同じく空白で区切られています。文字コードはUTF-8です。 1 NM_324891 sin3 associated polypeptide 2 NM_53344 Nanog homeobox . . . open (WRITE, ">RNAseq_Oct.csv"); open(FILE, "RNA.csv"); while($line = <FILE>){ chomp $line; @fact = split /\t/, $line; open(OCT, "oct_gene.csv"); while($octline = <OCT>){ chomp $octline; @oct = split /\t/, $octline; if($fact[2] =~ /$oct[1]/){ print WRITE $fact[2] . "\t" . $oct[1] ."\n"; last; } } close (OCT); } close (FILE); close (WRITE); この中のif文がうまく働かず、whileで繰り返すまでもなく必ず if($fact[2] =~ /$oct[1]/) が成り立ってしまいます。 どなたか詳しい方、どうかご教示願います。 それではよろしくお願いします。

    • ベストアンサー
    • Perl
  • データリストにデータが無い場合に追加する

    db.logファイルにgooのデータが無い場合に追加するcgiを作ってみたのですが、下記の様にダブって登録されてしまうのですが、ダブらずに登録するようにするにはどの様にすれば良いのでしょうか? サーバーはUNIXでWindowsXPからdb.logファイルをサーバーに転送させました。 それと$Check_list !~ m/($bb)/でリストにあるかないかを判断してるのですが、他に良いチェック方法ないでしょうか? <CGIアクセス前のdb.logファイル> yahoo;http://www.yahoo.co.jp; google;http://http://www.google.co.jp; <CGIアクセス後のdb.logファイル> yahoo;http://www.yahoo.co.jp; google;http://http://www.google.co.jp;goo;http://www.goo.ne.jp; goo;http://www.goo.ne.jp; <CGIファイル> #!/usr/bin/perl print "Content-type: text/html\n\n"; print<<"end"; <HTML><BODY> end open(Check_DB," < db.log"); while ($_=<Check_DB>){ ($a,$b) = split(/;/,$_); $Check_list.= "$b"; } close(Check_DB); $aa="goo"; $bb="http://www.goo.ne.jp"; if ($Check_list !~ m/($bb)/){ open(ADD_DB,">>db.log"); print ADD_DB "$aa;$bb;\n"; close(ADD_DB); } open(Print_DB," < db.log"); while ($_=<Print_DB>){ ($a,$b) = split(/;/,$_); print " サイト名 :$a URL :$b<BR>"; } close(Print_DB); print "</BODY></HTML>";

    • ベストアンサー
    • CGI
  • for文の応用

    今、S001.csvからS099.csvまでのファイルがあります。 これを下記のプログラムを書いて、新たにSA001.csvからSA099.csvまでのファイルを作りたいと考えています。 しかし、途中途中で数字が抜けているため、全てのファイルが処理できず 困っています。たとえば、S001.csvからS009.csvまでは 存在するが、S010.csvはなく、またS011.csvからは 存在するという具合です。 初歩的な質問で大変恐縮ですが、ご教示いただければ幸いです。 for ($i = 1; $i <= 99; ++$i) {  $file = sprintf("S%03d.csv", $i);  $newfile = sprintf("SA%03d.csv", $i); open (FILE, $file) or die; open (NEWFILE, ">$newfile") or die;  while (my $line = <FILE>) {  my ($aaa, $bbb, $ccc) = split(/,/, $line);  print NEWFILE join(',', $aaa, $bbb), "\n";  } close(FILE); close(NEWFILE); }

    • ベストアンサー
    • Perl
  • perlでのデータ処理について

    みなさんこんにちは、今、perlについてとても困っています。 以下に内容を記載しますので、わかる方がいましたら 良きアドバイスをお願いします。 #使用するファイルは2つです。 #元データ=$files1 #送信データ=$files2 #データの一行処理開始 open(IN,"$files2") || &error("$files1を開けません。"); while (<IN>) { ($no,$id,$mail,$type,$data)=split(/,/); # $files2を一行づつ読み込んで送信処理をしていきます。 . . . . . #ここからが問題点です。 # $files2を一行読み込んで送信が終わったら、$files1へ送信履歴として、 #日付を入力します。以下にスクリプトを記載しますので、良きアドバイスを #お願いします。 &get_date; @logs=(); @new=(); open(LOG,"$files1") || &error("$files1を開けません"); @logs = <LOG>; close(LOG); foreach $log (@logs){ @all_data = split(/,/,$log); if ($all_data[0] eq $id){ # $idは$files2から読み込んだ数値です。 $shori_date="$year\/$w_mon\/$w_mday"; $all_data[33]="($shori_date)($hour:$min)"; for ($i=0; $i<@all_data; $i++){ $work.="$all_data[$i],"; } chop $work; push(@new,$work); }else{ push(@new,$log); } } open(ALL,"+< $files1") || &error("$files1に書き込めません"); flock(ALL,2); truncate(ALL,0); seek(ALL,0,0); print ALL @new; close(ALL); #ここまでです。 #このようにすると、日付は入るのですが、データの更新、書き込みされたファイルを見ると #同じ内容が、2重、3重にコピーされて、ファイルに書き込みされてしまいます。 } close(IN); 以上なのですが、良きアドバイスを教えて下さい。

    • ベストアンサー
    • Perl
  • perlで容量の大きいCSVファイルを開く方法

    perlで容量の大きいCSVファイルを開く方法 ファイル容量の大きいcsvファイルから、必要な項目を抜き出して別ファイルにするプログラムを作成したいと思ってます。 csvファイルが少ない場合は動作したのですが、容量が140MBを超えたデータを読み込もうとすると、ブラウザー表示で何も変化いたしません プログラムは以下のようになってます。 ------------------------------------------------- 略 open(IN,"$inport") || &error(" $inport を読み込みopen出来ません"); flock(IN,1); @lines = <IN>; foreach $lines (@lines) { local(@val) = split("\,", $lines); print "$val[0]"; $dat .= "$val[1]\,$val[5]\n"; } open(OUT,">$dcsv"); flock(OUT,2); print OUT "$dat"; close OUT; 略 ------------------------------------------------- 件数も多いので、foreachを$lines (@lines) としないで($start .. $end)として読み込みの件数を制限して対応しようと考えてましたが、うまくいきませんでした。 ご指導いただけますと幸いです。

    • ベストアンサー
    • Perl
  • Perl 教えてください。

    下記のような簡単なスクリプトなんですが、なぜかサーバーにアップすると動きません。 ローカルでコマンドプロンプトから直接実行するとまともに動作します。 パーミッションは「755」と「705」でやってみましたが。 perlのパスは合っていますし、実際同じ場所に置いた他のCGIは動作します。 #!/usr/bin/perl open (DT, "<./data/***/***.csv") or die "File '***.csv' Open Error."; @data = <DT>; $data[0] = ",,,,,,\n"; open (OUT,">./data/***/***1.csv") or die "File '***1.csv' Open Error."; print OUT @data; close (DT); close (OUT); 原因が分からず困っています。 解決策でなく、原因と思えるだけの回答で結構ですので何卒よろしくお願いいたします。

    • ベストアンサー
    • Perl
  • cgiログファイルの書き込みに余計なスペースが入る。

    ---------- #ここでログファイルに書き込みを行う。 open(FH,">>log.log"); print FH"ID=$ID&COUNT=$COUNT\n"; close(FH); #ログファイルをさらに開く。 open FILE, "<log.log"; flock(FILE,2); @log = <FILE>; flock(FILE,8); close FILE; ログファイルを並び替えてその順番で書き込み。 $gyou = @log; @sort=sort{(split(/&/,$b))[1] cmp (split(/&/,$a))[1];} @log; print "<FONT COLOR=RED>@sort</FONT><BR>\n"; open(FILE2, ">log.log"); print FILE2"@sort"; close (FILE2); ---------- というように行っていますが、一番最初のID=01&とかの前に半角スペースが入ります。 下の行に行くほどスペースが増えます。 どこが原因でしょうか?

    • ベストアンサー
    • Perl
  • perlプログラム

    (1)のプログラムではtest.txtの内容を表示することができました。 (2)ではaaaが表示されつづけると思いますが、何も表示されません。 なにかまちがっていると思いますが、わかりません。 よろしくお願いします。 --------------------------------------------------------------------------------- (1) --------------------------------------------------------------------------------- sub Main() { LAST:while(){ if(defined(open(FILE,"test.txt"))){ flock(FILE,1); while(<FILE>){ print $_; last LAST if($_ == 0); } } sleep(1); } close(FILE); } &Main(); ------------------------------------------------------------------------------- (2) ------------------------------------------------------------------------------- sub Main() { LAST:while(){ if(defined(open(FILE,"test.txt"))){ flock(FILE,1); while(<FILE>){ print "aaa";  ←変更箇所 last LAST if($_ == 0); } } sleep(1); } close(FILE); } &Main(); -------------------------------------------------------------------------------