-PR-
解決済み

perl 複数ファイルから一行ずつ読み込んで比較

  • 困ってます
  • 質問No.6529798
  • 閲覧数9339
  • ありがとう数4
  • 気になる数0
  • 回答数4
  • コメント数0

お礼率 100% (4/4)

初心者向けのperl参考書がなんとか理解できる程度のperl初心者です。

一週間ほど思考錯誤してきたのですが、いくらスクリプトを書いてもどうしても自分では解決できない処理がありましたので、初めてなのですが、質問させていただきました。

二つの同じ形式の、一部情報だけ異なるファイルから一行ずつ配列を読み込んで、数値の大小を比較したいのですが・・・
ファイル構造は、下のように2行でひとつのtextデータの情報を表示する形式となってまして、
奇数行には、各textファイルの情報が、.以下の部分に4552221.2:(30.2):100、のように記載されています。
偶数行には、各テキストファイルの、IDとなる情報が、13333331のように記載されています。この奇数行と、偶数行一行ずつで、ひとつのtextデータの
情報を表しています。textというファイルは、text1から順に,text2000000まで、
2000万程度,行で言えば4000万行ほど存在しています。よって、file1もfile2も、同じ行であれば、同じtextで、同じIDのものを示しています。ずれはありません。



file1
-----------------------------
>text1. 4552221.2:(30.2):100
13333331
>text2
87999999
>text3. 3444444.1:(20.0):300
75533333

-----------------------------

数値は意味が変わらない範囲で、比較しやすい数値に変えてあります。
この上のfile1と、下のfile2を比べたいのですが、


file2
-----------------------------
>text1. 4552221.2:(20.9):100
13333331
>text2
87999999
>text3. 3444444.1:(23.3):300
75533333

-----------------------------




偶数行のtextファイルの欄が大事で、text名のドット(.)の後に、続けて数値が書いてある場合(上ではtext1とtext3が該当)、そのドットの後の括弧()で囲まれた中の数値の大小を比較して、
差が一定以上あるtextだけを出力するスクリプトが書きたいと思っているのですが、
これをいきなりすべてひとつのスクリプトにまとめるのは、私の知識と力量では到底無理なので、ひとつひとつ段階を踏んで処理していこうと考えました。



1、奇数行の、text.の後半に情報があるもの場合、tempファイルにその奇数行と、対になる偶数行を出力する。それを、file1、file2、個別に行う。(temp1、temp2を出力)



file1で取り除かれる3、4行目のtext2は、file2でも必ず取り除かれるので、1の処理後も、file1と2の各行は、比較することが可能な状態です。



2、正規表現を使って、各行の()で囲まれた部分の最初の2桁の数字(一部は一桁の数字の場合もあり)、をtempに出力する。(temp3、temp4を出力)
(この処理を行わなくても可能かもしれませんが、ややこしいので、数字だけ出すようにしました)





最後に、file1とfile2の各行の数字を一行ずつ読み込んで、数値に一定以上の差がある行の情報だけ出力したいのですが、各行を順番に分析できる方法は、while文か、配列に読み込む方法しか知りません。
前者のwhile文では、ファイルオープンは、ひたつ以上は同時には取り扱えないという情報を得たので、使えない思っています。後者の配列に読み込む方法は、桁が大きいので、実用的ではないと感じています。
二つ以上のファイルの各行の特定の数値データを、一行ずつ順に比較して、差があるものだけ抽出するにはどのようなスクリプトを書けばよいのか、ご教授下さい。

いろいろ調べましたが、(僕の調べ方が悪いと思いますが)適切なコマンドや方法にたどり着けません。



試してみた方法

ファイルハンドルを二つ指定して、while文の条件中に、andで条件を二つ指定してファイルハンドルから読み込んでやればよいと思ったのですが、実行するとエラーになってしまいます。ググっていろいろ調べてみたところ、ファイルハンドルの二つ指定はできないとの記述を見つけたので、whileの条件に複数のファイルハンドルを記述するこの方法はあきらめて、ここで、ストップしています。


まわりに、perlを扱える方がいらっしゃれば質問に伺うのですが。もしよろしければ、簡単でもご教授いただけますと幸いです。宜しくお願い申し上げます。
通報する
  • 回答数4
  • 気になる
    質問をブックマークします。
    マイページでまとめて確認できます。

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

  • 回答No.2
レベル11

ベストアンサー率 64% (120/185)

元のファイルの file1 と file2 の対応関係にズレはないということなので、1つの while ループで処理できると思います。

#!/usr/bin/perl
use strict;
use warnings;

open IN_1, "file1" or die "Can't open file1: $!";
open IN_2, "file2" or die "Can't open file2: $!";
open OUT, ">output.txt" or die "Can't open output.txt: $!";

while (my $f1_line = <IN_1>) {
$f1_line .= <IN_1>;
my $f2_line = <IN_2>; $f2_line .= <IN_2>;
next unless $f1_line =~ /\([\d.]+\)/;
my ($num_1) = $f1_line =~ /\(([\d.]+)\)/;
my ($num_2) = $f2_line =~ /\(([\d.]+)\)/;
if (abs($num_1 - $num_2) >= 5) { # 5 とブロック内は適宜変更
print OUT $f1_line, $f2_line;
}
}

close IN_1; close IN_2; close OUT;
お礼コメント
kazumaxneo

お礼率 100% (4/4)

お返事が遅くなってしまい、申し訳ありません。こまかい部分で、いくつか良く分からない部分がありましたので、先頭数百行でとりあえずサンプルファイルを作って、それでご連絡いただいたスクリプトを使って処理してみました。

結果からいうとうまく出力できました。いまだなぜこのように書くのか?不明な点はありますが、このような短い記述で、差があったtextファイルだけ出力できたので、大変驚いています。本当にありがとうございます。特に参考になったのは、perlを使える皆様には当たり前の話かもしれませんが




1、whileの中に、ファイルハンドルを宣言するならば、ひとつのファイルハンドルで複数のファイルを処理できる。当たり前かもしれませんがこの発想に頭がまったく回りませんでした。


2、nextとunlessを組み合わせて、正規表現でマッチしなければ、whileの処理をスキップする部分。
next unless $f1_line =~ /\([\d.]+\)/;


3、マッチすれば、それを次の処理につなげるためそのまま新変数に代入する
my ($num_1) = $f1_line =~ /\(([\d.]+)\)/;


4、新変数の差を条件にif文を走らせる。


本当に勉強になりました。ひとつの疑問でいろいろと学べ、まことに感謝しております。お忙しい中、このような素晴らしい記述をご教授いただき、まことにありがとうございました。


ところで、後学のためにお時間ありましたらもうひとつだけ教えていただきたいのですが、while文の中で、再度$f1_lineを定義しているのはどういう意味があるのでしょうか?IN2もそうですが。
while (my $f1_line = <IN_1>) {
$f1_line .= <IN_1>;

また、その際、=でなく.=としているのはどういう意味があるのでしょうか?自分なりに検索して調べてみましたが、.=というものの意味を示す記述は見つかりませんでした。これが分かれば、この記述法は、完全に理解できると思うのですが、ここだけよく分かりません。
投稿日時 - 2011-02-18 19:47:45

その他の回答 (全3件)

  • 回答No.4
レベル12

ベストアンサー率 42% (281/661)

二回目です。
大量のファイルを入力にして複雑な操作をするときのコツです。

(1)1本のプログラムで全部の処理をやろうとしない

細かい処理に分けて、1個1個を解こうとする。
具体的には中間ファイルを作る。

A=>Z

という処理を A=>B、B=>C・・・と小分割し、1個1個を確実に検証する。
分からないところは、わからない部分だけ人の助けを求める。

(2) 少ない量のデータでテストして、完璧に走らせ、それが出来てはじめて、大量なデータに挑戦する。

これをまずやってから人に聞いた方がお互いのためです。
お礼コメント
kazumaxneo

お礼率 100% (4/4)

さらに追記連絡していただきありがとうございます。われながら、的を得ないへたくそな質問だと思いました。もう少し、的を絞ってコンパクトにまとめる更なる努力が必要だと感じました。

ただ、あえて解決済みの部分も含めて全体で質問させていただきましたのは、私の記述法では、いろいろ無駄がありすぎる気がしまして、文章処理に強いperlならば、もっと短く明朗快活に表現できるのではないか、と期待していた部分があります。とはいえ、貴重な時間を割いて回答してもらうのですからこのような質問姿勢ではダメですよね。

またどうしても解決できない問題がありましたら、これからもまた新しく質問投稿してしまうかもしれません。その際も、もしお時間ございましたら、またお相手していただければ幸いです。

1と2のコツは、たいへんタメになりました。大規模なデータですし、常にそういった配慮を忘れないようにしてがんばります。

ありがとうございました
投稿日時 - 2011-02-18 20:08:03


  • 回答No.1
レベル12

ベストアンサー率 42% (281/661)

全部読むのは大変なので読みませんでした。すみません。

open FILE, $file;
while (<FILE>) {
  $_の処理
}
close FILE;

という処理の意味は分かりますね。
これと、

open FILE, $file;
@arr = <FILE>;
close <FILE>;

for (@arr) {
  $_の処理
}

という処理はまったく同じです。
下では@arrという配列にファイルの中身をドバーンと入れて処理しています。
これだと、

open FILE, $file_1;
@arr1 = <FILE>;
close <FILE>;

open FILE, $file_2;
@arr2 = <FILE>;
close <FILE>;

という風に複数のファイルを処理するのも簡単ですし、

for $i (0 .. $#arr) { # 0 から $#arr まで番号を$iに入れて処理する。$#arrは配列の最後の指標。
  next if $i % 2 == 1; # $i を2で割った余りが1だったらループを飛ばす
  $arr[$i] の処理
}

と言う風にやれば奇数の行の処理も出来るでしょう。

大きな問題を小さな問題に分解する考え方はいいんですが、だったらその小さな問題のうち、うまくいかない部分だけを質問してください。
じゃないと質問に答える人はウンザリしてしまいます。

それから、エラーが出たというだけではなくて、実際にどんなプログラムを動かしたら、なんというエラーが出たかも教えてください。
補足コメント
kazumaxneo

お礼率 100% (4/4)

補足というか、文字制限で書けませんので追記しています。私の書いた、上のつたないスクリプトでも、
抽出の対象となる、下のような構造の二つのファイルから、

file1
>text1. 4552221.2:(30.2):100
13333331
>text2
87999999
>text3. 3444444.1:(20.0):300
75533333






file2
>text1. 4552221.2:(20.9):100
13333331
>text2
87999999
>text3. 3444444.1:(23.3):300
75533333





から、()内の左端の2桁の数値だけ抽出して、下のように、一行ずつ出力することまでは
とりあえず、エラーなくできています。

file1(temp3として出力)
30
20



file2(temp4として出力)
20
23



1000万以上の行のテキストの場合、どの程度の実用性があるのか不明ではありますし、もっとコンパクトに出来るとは思うのですが。

一番お聞きしたいのは、この2つのファイルの各行の数値を順に比較して、数値に一定以上の差がある場合、その数値を出力する、というのをどのような表現やコマンドを用いればできるのか?、です。

しつこくて大変恐縮なのですが、どなたかぜひ教えて下さい。お願いします。whileでのループや、forでの全配列でのループ処理では、file1かfile2のどちらか一方を順に処理することはできますが、どちらのファイルも、一行ずつ交互に読み込んで処理するにはどうすれば良いのでしょうか?このような考え方自体、根本的に間違っていたら申しわけありません。
投稿日時 - 2011-02-17 18:07:41
お礼コメント
kazumaxneo

お礼率 100% (4/4)

貴重な時間を割いて早速お答ええいただき、ありがとうございます。たいへん迅速にお答えいただいたので驚いています。

配列に読み込むという方法、やってみます。まずは、100行程度だけ抽出したテストファイルを用いて試して見ます。
ただこの方法だと、考え方が間違っているかもしれませんが、行が1000万以上ある場合、一度メモリにすべてキャッシュされ、計算機に負担が大きいのではないかと懸念しています。ただ、配列に読み込む方法は、ぼんやりとはしってましたが、書くこともテストしたこともないので、大変勉強になりました。重ね重ねありがとうございます。今から試してみます。

>大きな問題を小さな問題に分解する考え方はいいんですが、だったらその小さな問題のうち、うまくいかない部分だけを質問してください。じゃないと質問に答える人はウンザリしてしまいます。

自分も投稿後に感じました。申しわけないと思っています。

>それから、エラーが出たというだけではなくて、実際にどんなプログラムを動かしたら、なんというエラーが出たかも教えてください。

質問欄にスクリプトを記載するには、文字制限でできませんでした。不親切すぎて回答に困りますよね。すいませんでした。
以下が、書いてみたスクリプト原文です。ここまででは、エラーは出てきませんでした。

#!/usr/bin/perl
use strict;
use warnings;

#まずは、後半に情報があるtextファイルだけ抜き出す文を書く

#入力、出力のファイルハンドルを指定
open (IN1, 'file1') or die "$!";
open (IN2, 'file2') or die "$!";
open (OUT1, '>temp1') or die "$!";
open (OUT2, '>temp2') or die "$!";

#if内で定数を定義したらエラーになるので、ここで定義。
my $flg1 = 0;
my $flg2 = 0;

#まずはfile1から
while (my $data1 = <IN1>){

#()があれば、$flg1に2を代入
 if (data1 =~ /\(.*\)/){
 $flg1 = 2;
 }

#$flgが0以上なら出力し、flgの値を1減らす。真なら2行続けて出力される。
 if ($flg1 > 0){
 print OUT1 $data1;
 $flg1 = $flg1 -1;
}
}



#file2も同様に処理する
while (my $data2 = <IN1>){

#()があれば、$flg2に2を代入
 if (data2 =~ /\(.*\)/){
 $flg2 = 2;
 }

#$flgが0以上なら出力し、flgの値を1減らす。真なら2行続けて出力される。
 if ($flg2 > 0){
 print OUT2 $data2;
 $flg2 = $flg2 -1;
}
}

close IN1;
close IN2;
close OUT1;
close OUT2;


#各行の()で囲まれた部分の最初の2桁の数字だけ抜き出す

#入力、出力のファイルハンドルを指定
open (IN3, 'temp1') or die "$!";
open (IN4, 'temp2') or die "$!";
open (OUT3, '>temp3') or die "$!";
open (OUT4, '>temp4') or die "$!";

while (my $data3 = <IN3>){
#(数字2桁 )があれば真、数字2桁を変数に代入。
if ($seq3 =~ /(\()([1-9][1-9])/{
my $none1 = $1;
my $compare1 = $2;
#改行を挿入
my $compare2 = "$compare1\n";
print OUT3 $compare2;
}


while (my $data4 = <IN4>){
#(数字2桁 )があれば真、数字2桁を変数に代入。
if ($data4 =~ /(\()([1-9][1-9])/{
my $none2 = $1;
my $compare3 = $2;
#改行を挿入
my $compare4 = "$compare3\n";
print OUT4 $compare4;
}
close IN3;
close IN4;
close OUT3;
close OUT4;


#次に、各行の数値の大小を比較する。この先、2ファイルの同じ行を
#比較する方法が分かりませんでした。

exit
投稿日時 - 2011-02-17 16:57:17
  • 回答No.3
レベル11

ベストアンサー率 49% (130/262)

ファイルが2000000個あるわけですから、推定所用時間はちゃんと計算した方がいいですよ。

それほどの規模なら、データベースに放り込んでSQLで片付けるのが早いです。
現実的な時間で処理してくれると思います。
お礼コメント
kazumaxneo

お礼率 100% (4/4)

お返事ありがとうございます。excelのRなら、微々たるものですが、なんとか理解できるのですが、SQLというのはほぼ初耳でした。今後、こうした数千万行の処理を行う機会が増えそうなので、回答者様方に一歩でも近づけるように、日々努力を続けたいと思います。

参考というか、すごく役に立ちました!
投稿日時 - 2011-02-18 19:56:22
このQ&Aで解決しましたか?
AIエージェント「あい」

こんにちは。AIエージェントの「あい」です。
あなたの悩みに、OKWAVE 3,500万件のQ&Aを分析して最適な回答をご提案します。

関連するQ&A
こんな書き方もあるよ!この情報は知ってる?あなたの知識を教えて!
このQ&Aにはまだコメントがありません。
あなたの思ったこと、知っていることをここにコメントしてみましょう。

その他の関連するQ&A、テーマをキーワードで探す

キーワードでQ&A、テーマを検索する
-PR-

特集


抽選で合計100名様にプレゼント!

ピックアップ

ページ先頭へ